Merge "Various fixes to ARM rootfs scripts"
diff --git a/Android.mk b/Android.mk
index 6b5ab61..59e9806 100644
--- a/Android.mk
+++ b/Android.mk
@@ -21,3 +21,8 @@
 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/CleanSpec.mk b/CleanSpec.mk
index 4c1985d..f235841 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -66,3 +66,5 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/bin/hw/android.hardware.health.storage@1.0-service.cuttlefish)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/android.hardware.health.storage@1.0-service.cuttlefish.rc)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/vintf/manifest/manifest_android.hardware.health.storage@1.0.cuttlefish.xml)
+
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/apex/com.android.gki.*)
diff --git a/README.md b/README.md
index aa63bba..2220898 100644
--- a/README.md
+++ b/README.md
@@ -30,10 +30,10 @@
 3. Go to http://ci.android.com/
 4. Enter a branch name. Start with `aosp-master` if you don't know what you're
    looking for
-5. Navigate to `aosp_cf_x86_phone` and click on `userdebug` for the latest build
+5. Navigate to `aosp_cf_x86_64_phone` and click on `userdebug` for the latest build
 6. Click on `Artifacts`
 7. Scroll down to the OTA images. These packages look like
-   `aosp_cf_x86_phone-img-xxxxxx.zip` -- it will always have `img` in the name.
+   `aosp_cf_x86_64_phone-img-xxxxxx.zip` -- it will always have `img` in the name.
    Download this file
 8. Scroll down to `cvd-host_package.tar.gz`. You should always download a host
    package from the same build as your images.
@@ -43,7 +43,7 @@
    mkdir cf
    cd cf
    tar xvf /path/to/cvd-host_package.tar.gz
-   unzip /path/to/aosp_cf_x86_phone-img-xxxxxx.zip
+   unzip /path/to/aosp_cf_x86_64_phone-img-xxxxxx.zip
    ```
 
 10. Launch cuttlefish with:
@@ -60,10 +60,18 @@
 
    `$ ./bin/adb -e shell`
 
-## Launch Viewer
+## Launch Viewer (WebRTC)
 
-You can use the [TightVNC JViewer](https://www.tightvnc.com/download.php). Once
-you have downloaded the *TightVNC Java Viewer JAR in a ZIP archive*, run it with
+When launching with `---start_webrtc` (the default), you can see a list of all
+available devices at `https://localhost:8443` . For more information, see the
+WebRTC on Cuttlefish
+[documentation](https://source.android.com/setup/create/cuttlefish-ref-webrtc).
+
+## Launch Viewer (VNC)
+
+When launching with `--start_vnc_server=true` , You can use the
+[TightVNC JViewer](https://www.tightvnc.com/download.php). Once you have
+downloaded the *TightVNC Java Viewer JAR in a ZIP archive*, run it with
 
    `$ java -jar tightvnc-jviewer.jar -ScalingFactor=50 -Tunneling=no -host=localhost -port=6444`
 
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4067d27..889dfbe 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -19,6 +19,12 @@
     },
     {
       "name": "vts_ibase_test"
+    },
+    {
+      "name": "CtsScopedStorageDeviceOnlyTest"
+    },
+    {
+      "name": "MediaProviderTests"
     }
   ]
 }
diff --git a/build/Android.bp b/build/Android.bp
index 76b8480..b824a2b 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -43,12 +43,18 @@
     "config_server",
     "console_forwarder",
     "crosvm",
+    "cvd",
+    "cvd_internal_host_bugreport",
+    "cvd_internal_start",
+    "cvd_internal_status",
+    "cvd_internal_stop",
     "cvd_host_bugreport",
     "cvd_status",
     "extract-ikconfig",
     "extract-vmlinux",
     "fsck.f2fs",
     "gnss_grpc_proxy",
+    "health",
     "kernel_log_monitor",
     "launch_cvd",
     "libgrpc++",
@@ -60,6 +66,7 @@
     "lz4",
     "make_f2fs",
     "metrics",
+    "mkbootfs",
     "mkbootimg",
     "mkenvimage",
     "modem_simulator",
@@ -75,8 +82,8 @@
     "secure_env",
     "socket_vsock_proxy",
     "stop_cvd",
-    "tapsetiff",
     "tombstone_receiver",
+    "toybox",
     "unpack_bootimg",
     "vnc_server",
     "webRTC",
@@ -99,6 +106,7 @@
     "webrtc_controls.js",
     "webrtc_cf.js",
     "webrtc_index.html",
+    "webrtc_rootcanal.js",
     "webrtc_server.crt",
     "webrtc_server.key",
     "webrtc_server.p12",
@@ -157,6 +165,12 @@
     "xhci.policy_aarch64",
 ]
 
+cvd_host_qemu_bootloader = [
+    "bootloader_qemu_x86_64",
+    "bootloader_qemu_aarch64",
+    "bootloader_qemu_arm",
+]
+
 cvd_host_package_customization {
     name: "cvd-host_package",
     deps: cvd_host_tools +
@@ -165,6 +179,7 @@
         common: {
             deps: cvd_host_webrtc_assets +
                 cvd_host_model_simulator_files +
+                cvd_host_qemu_bootloader +
                 cvd_bluetooth_config_files,
         },
     },
diff --git a/common/frontend/socket_vsock_proxy/main.cpp b/common/frontend/socket_vsock_proxy/main.cpp
index 590305b..cf68821 100644
--- a/common/frontend/socket_vsock_proxy/main.cpp
+++ b/common/frontend/socket_vsock_proxy/main.cpp
@@ -40,6 +40,10 @@
                                  "monitor before creating a tcp-vsock tunnel."
                                  "This option is used by --server=tcp only "
                                  "when socket_vsock_proxy runs as a host service");
+DEFINE_int32(
+    server_fd, -1,
+    "A file descriptor. If set the passed file descriptor will be used as the "
+    "server and the corresponding port flag will be ignored");
 
 namespace {
 // Sends packets, Shutdown(SHUT_WR) on destruction
@@ -169,7 +173,14 @@
 [[noreturn]] void TcpServer() {
   LOG(DEBUG) << "starting TCP server on " << FLAGS_tcp_port
              << " for vsock port " << FLAGS_vsock_port;
-  auto server = cuttlefish::SharedFD::SocketLocalServer(FLAGS_tcp_port, SOCK_STREAM);
+  cuttlefish::SharedFD server;
+  if (FLAGS_server_fd < 0) {
+    server =
+        cuttlefish::SharedFD::SocketLocalServer(FLAGS_tcp_port, SOCK_STREAM);
+  } else {
+    server = cuttlefish::SharedFD::Dup(FLAGS_server_fd);
+    close(FLAGS_server_fd);
+  }
   CHECK(server->IsOpen()) << "Could not start server on " << FLAGS_tcp_port;
   LOG(DEBUG) << "Accepting client connections";
   int last_failure_reason = 0;
@@ -224,13 +235,18 @@
 [[noreturn]] void VsockServer() {
   LOG(DEBUG) << "Starting vsock server on " << FLAGS_vsock_port;
   cuttlefish::SharedFD vsock;
-  do {
-    vsock = cuttlefish::SharedFD::VsockServer(FLAGS_vsock_port, SOCK_STREAM);
-    if (!vsock->IsOpen() && !socketErrorIsRecoverable(vsock->GetErrno())) {
-      LOG(ERROR) << "Could not open vsock socket: " << vsock->StrError();
-      SleepForever();
-    }
-  } while (!vsock->IsOpen());
+  if (FLAGS_server_fd < 0) {
+    do {
+      vsock = cuttlefish::SharedFD::VsockServer(FLAGS_vsock_port, SOCK_STREAM);
+      if (!vsock->IsOpen() && !socketErrorIsRecoverable(vsock->GetErrno())) {
+        LOG(ERROR) << "Could not open vsock socket: " << vsock->StrError();
+        SleepForever();
+      }
+    } while (!vsock->IsOpen());
+  } else {
+    vsock = cuttlefish::SharedFD::Dup(FLAGS_server_fd);
+    close(FLAGS_server_fd);
+  }
   CHECK(vsock->IsOpen()) << "Could not start server on " << FLAGS_vsock_port;
   while (true) {
     LOG(DEBUG) << "waiting for vsock connection";
@@ -255,8 +271,11 @@
 #endif
   google::ParseCommandLineFlags(&argc, &argv, true);
 
-  CHECK(FLAGS_tcp_port != 0) << "Must specify -tcp_port flag";
-  CHECK(FLAGS_vsock_port != 0) << "Must specify -vsock_port flag";
+  CHECK((FLAGS_server == "tcp" && FLAGS_server_fd >= 0) || FLAGS_tcp_port != 0)
+      << "Must specify -tcp_port or -server_fd (with -server=tcp) flag";
+  CHECK((FLAGS_server == "vsock" && FLAGS_server_fd >= 0) ||
+        FLAGS_vsock_port != 0)
+      << "Must specify -vsock_port or -server_fd (with -server=vsock) flag";
 
   if (FLAGS_adbd_events_fd >= 0) {
     LOG(DEBUG) << "Wating AdbdStarted boot event from the kernel log";
diff --git a/common/libs/concurrency/multiplexer.h b/common/libs/concurrency/multiplexer.h
new file mode 100644
index 0000000..478963f
--- /dev/null
+++ b/common/libs/concurrency/multiplexer.h
@@ -0,0 +1,88 @@
+/*
+ * 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 <condition_variable>
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include "common/libs/concurrency/semaphore.h"
+#include "common/libs/concurrency/thread_safe_queue.h"
+
+namespace cuttlefish {
+template <typename T, typename Queue>
+class Multiplexer {
+ public:
+  using QueuePtr = std::unique_ptr<Queue>;
+  using QueueSelector = std::function<int(void)>;
+
+  template <typename... Args>
+  static QueuePtr CreateQueue(Args&&... args) {
+    auto raw_ptr = new Queue(std::forward<Args>(args)...);
+    return QueuePtr(raw_ptr);
+  }
+
+  Multiplexer() : sem_items_{0} {}
+
+  int RegisterQueue(QueuePtr&& queue) {
+    const int id_to_return = queues_.size();
+    queues_.push_back(std::move(queue));
+    return id_to_return;
+  }
+
+  void Push(const int idx, T&& t) {
+    CheckIdx(idx);
+    queues_[idx]->Push(std::move(t));
+    sem_items_.SemPost();
+  }
+
+  T Pop(QueueSelector selector) {
+    SemWait();
+    int q_id = selector();
+    CheckIdx(q_id);  // check, if weird, will die there
+    QueuePtr& queue = queues_[q_id];
+    CHECK(queue) << "queue must not be null.";
+    return queue->Pop();
+  }
+
+  T Pop() {
+    auto default_selector = [this]() -> int {
+      for (int i = 0; i < queues_.size(); i++) {
+        if (!queues_[i]->IsEmpty()) {
+          return i;
+        }
+      }
+      return -1;
+    };
+    return Pop(default_selector);
+  }
+
+  bool IsEmpty(const int idx) { return queues_[idx]->IsEmpty(); }
+
+  void SemWait() { sem_items_.SemWait(); }
+
+ private:
+  void CheckIdx(const int idx) {
+    CHECK(idx >= 0 && idx < queues_.size()) << "queues_ array out of bound";
+  }
+  // total items across the queues
+  Semaphore sem_items_;
+  std::vector<QueuePtr> queues_;
+  QueuePtr null_ptr_;
+};
+}  // end of namespace cuttlefish
diff --git a/common/libs/concurrency/thread_safe_queue.h b/common/libs/concurrency/thread_safe_queue.h
index 9105299..ae16bef 100644
--- a/common/libs/concurrency/thread_safe_queue.h
+++ b/common/libs/concurrency/thread_safe_queue.h
@@ -20,6 +20,7 @@
 #include <deque>
 #include <iterator>
 #include <mutex>
+#include <type_traits>
 #include <utility>
 
 namespace cuttlefish {
@@ -34,9 +35,11 @@
 class ThreadSafeQueue {
  public:
   using QueueImpl = std::deque<T>;
+  using QueueFullHandler = std::function<void(QueueImpl*)>;
+
   ThreadSafeQueue() = default;
   explicit ThreadSafeQueue(std::size_t max_elements,
-                           std::function<void(QueueImpl*)> max_elements_handler)
+                           QueueFullHandler max_elements_handler)
       : max_elements_{max_elements},
         max_elements_handler_{std::move(max_elements_handler)} {}
 
@@ -58,18 +61,17 @@
     return std::move(items_);
   }
 
-  void Push(T&& t) {
+  template <typename U>
+  bool Push(U&& u) {
+    static_assert(std::is_assignable_v<T, decltype(u)>);
     std::lock_guard<std::mutex> guard(m_);
-    DropItemsIfAtCapacity();
-    items_.push_back(std::move(t));
+    const bool has_room = DropItemsIfAtCapacity();
+    if (!has_room) {
+      return false;
+    }
+    items_.push_back(std::forward<U>(u));
     new_item_.notify_one();
-  }
-
-  void Push(const T& t) {
-    std::lock_guard<std::mutex> guard(m_);
-    DropItemsIfAtCapacity();
-    items_.push_back(t);
-    new_item_.notify_one();
+    return true;
   }
 
   bool IsEmpty() {
@@ -83,15 +85,22 @@
   }
 
  private:
-  void DropItemsIfAtCapacity() {
+  // return whether there's room to push
+  bool DropItemsIfAtCapacity() {
     if (max_elements_ && max_elements_ == items_.size()) {
       max_elements_handler_(&items_);
     }
+    if (max_elements_ && max_elements_ == items_.size()) {
+      // handler intends to ignore the newly coming element or
+      // did not empty the room for whatever reason
+      return false;
+    }
+    return true;
   }
 
   std::mutex m_;
   std::size_t max_elements_{};
-  std::function<void(QueueImpl*)> max_elements_handler_{};
+  QueueFullHandler max_elements_handler_{};
   std::condition_variable new_item_;
   QueueImpl items_;
 };
diff --git a/common/libs/confui/packet.cpp b/common/libs/confui/packet.cpp
index 6f6400a..6f0c3eb 100644
--- a/common/libs/confui/packet.cpp
+++ b/common/libs/confui/packet.cpp
@@ -30,8 +30,9 @@
 namespace packet {
 ConfUiMessage PayloadToConfUiMessage(const std::string& str_to_parse) {
   auto tokens = android::base::Split(str_to_parse, ":");
-  PassOrDie(tokens.size() >= 3, "PayloadToConfUiMessage takes \"", str_to_parse,
-            "\" and does not have 3 tokens");
+  ConfUiCheck(tokens.size() >= 3)
+      << "PayloadToConfUiMessage takes \"" + str_to_parse + "\""
+      << "and does not have 3 tokens";
   std::string msg;
   std::for_each(tokens.begin() + 2, tokens.end() - 1,
                 [&msg](auto& token) { msg.append(token + ":"); });
@@ -109,6 +110,26 @@
   return ReadPayload(fd);
 }
 
+std::optional<std::tuple<bool, std::string>> RecvAck(
+    SharedFD fd, const std::string& session_id) {
+  auto conf_ui_msg = RecvConfUiMsg(fd);
+  if (!conf_ui_msg) {
+    ConfUiLog(ERROR) << "Received Ack failed due to communication.";
+    return std::nullopt;
+  }
+  auto [recv_session_id, type, contents] = conf_ui_msg.value();
+  if (session_id != recv_session_id) {
+    ConfUiLog(ERROR) << "Received Session ID is not the expected one,"
+                     << session_id;
+    return std::nullopt;
+  }
+  if (ToCmd(type) != ConfUiCmd::kCliAck) {
+    ConfUiLog(ERROR) << "Received cmd is not ack but " << type;
+    return std::nullopt;
+  }
+  return FromCliAckCmd(contents);
+}
+
 bool SendCmd(SharedFD fd, const std::string& session_id, ConfUiCmd cmd,
              const std::string& additional_info) {
   return WritePayload(fd, cmd, session_id, additional_info);
@@ -116,8 +137,8 @@
 
 bool SendAck(SharedFD fd, const std::string& session_id, const bool is_success,
              const std::string& additional_info) {
-  return WritePayload(fd, ConfUiCmd::kCliAck, session_id,
-                      ToCliAckMessage(is_success, additional_info));
+  return SendCmd(fd, session_id, ConfUiCmd::kCliAck,
+                 ToCliAckMessage(is_success, additional_info));
 }
 
 bool SendResponse(SharedFD fd, const std::string& session_id,
diff --git a/common/libs/confui/packet.h b/common/libs/confui/packet.h
index 848eb29..119f102 100644
--- a/common/libs/confui/packet.h
+++ b/common/libs/confui/packet.h
@@ -55,6 +55,9 @@
 ConfUiMessage PayloadToConfUiMessage(const std::string& str_to_parse);
 
 std::optional<ConfUiMessage> RecvConfUiMsg(SharedFD fd);
+std::optional<std::tuple<bool, std::string>> RecvAck(
+    SharedFD fd, const std::string& session_id);
+
 bool SendAck(SharedFD fd, const std::string& session_id, const bool is_success,
              const std::string& additional_info);
 bool SendResponse(SharedFD fd, const std::string& session_id,
diff --git a/common/libs/confui/protocol.cpp b/common/libs/confui/protocol.cpp
index 6784034..62678d4 100644
--- a/common/libs/confui/protocol.cpp
+++ b/common/libs/confui/protocol.cpp
@@ -50,6 +50,9 @@
 }
 
 std::string ToString(const ConfUiCmd& cmd) { return ToDebugString(cmd, false); }
+std::string ToString(const ConfUiMessage& msg) {
+  return "[" + msg.session_id_ + ", " + msg.type_ + ", " + msg.msg_ + "]";
+}
 
 ConfUiCmd ToCmd(std::uint32_t i) {
   std::vector<ConfUiCmd> all_cmds{
@@ -82,6 +85,24 @@
   return ConfUiCmd::kUnknown;
 }
 
+std::optional<std::tuple<bool, std::string>> FromCliAckCmd(
+    const std::string& message) {
+  auto colon_pos = message.find_first_of(":");
+  if (colon_pos == std::string::npos) {
+    ConfUiLog(ERROR) << "Received message, \"" << message
+                     << "\" is ill-formatted ";
+    return std::nullopt;
+  }
+  std::string header = message.substr(0, colon_pos);
+  std::string msg = message.substr(colon_pos + 1);
+  if (header != "error" && header != "success") {
+    ConfUiLog(ERROR) << "Received message, \"" << message
+                     << "\" has a wrong header";
+    return std::nullopt;
+  }
+  return {std::tuple{(header == "success"), msg}};
+}
+
 std::string ToCliAckMessage(const bool is_success, const std::string& message) {
   std::string header = "error:";
   if (is_success) {
diff --git a/common/libs/confui/protocol.h b/common/libs/confui/protocol.h
index 582b43e..f3b3f6e 100644
--- a/common/libs/confui/protocol.h
+++ b/common/libs/confui/protocol.h
@@ -16,7 +16,9 @@
 #pragma once
 
 #include <cstdint>
+#include <optional>
 #include <string>
+#include <tuple>
 
 namespace cuttlefish {
 namespace confui {
@@ -49,6 +51,9 @@
 // invalid/ignored session id
 constexpr char SESSION_ANY[] = "";
 
+std::optional<std::tuple<bool, std::string>> FromCliAckCmd(
+    const std::string& message);
+
 std::string ToCliAckMessage(const bool is_success, const std::string& message);
 std::string ToCliAckErrorMsg(const std::string& message);
 std::string ToCliAckSuccessMsg(const std::string& message);
@@ -58,6 +63,7 @@
   std::string type_;  // cmd, which cmd? ack, response, etc
   std::string msg_;
 };
+std::string ToString(const ConfUiMessage& msg);
 
 }  // end of namespace confui
 }  // end of namespace cuttlefish
diff --git a/common/libs/confui/utils.h b/common/libs/confui/utils.h
index c5e3d88..2e4ac04 100644
--- a/common/libs/confui/utils.h
+++ b/common/libs/confui/utils.h
@@ -44,29 +44,11 @@
   return ArgsToStringWithDelim("", std::forward<Args>(args)...);
 }
 
-template <typename... Args>
-std::string ToConLog(Args&&... args) {
-  return ArgsToStringWithDelim(" ", "ConfUI: ", std::forward<Args>(args)...);
-}
+// note that no () surrounding LOG(level) << "ConfUI:" is crucial
+#define ConfUiLog(LOG_LEVEL) LOG(LOG_LEVEL) << "ConfUI: "
 
 // TODO(kwstephenkim@google.com): make these look more like LOG(level)
-#define DEFINE_CONFUI_LOG_FUNC(NAME, LOG_LEVEL)              \
-  template <typename... Args>                                \
-  void NAME##Log(Args&&... args) {                           \
-    LOG(LOG_LEVEL) << ToConLog(std::forward<Args>(args)...); \
-  }
-
-DEFINE_CONFUI_LOG_FUNC(Fatal, FATAL)
-DEFINE_CONFUI_LOG_FUNC(Error, ERROR)
-DEFINE_CONFUI_LOG_FUNC(Warning, WARNING)
-DEFINE_CONFUI_LOG_FUNC(Debug, DEBUG)
-DEFINE_CONFUI_LOG_FUNC(Info, INFO)
-DEFINE_CONFUI_LOG_FUNC(Verbose, VERBOSE)
-
-template <typename... Args>
-void PassOrDie(bool cond, Args&&... args) {
-  CHECK(cond) << ToConLog(std::forward<Args>(args)...);
-}
+#define ConfUiCheck(cond) CHECK(cond) << "ConfUI: "
 
 }  // end of namespace confui
 }  // end of namespace cuttlefish
diff --git a/common/libs/device_config/host_device_config.cpp b/common/libs/device_config/host_device_config.cpp
index 35ec27f..eb28c63 100644
--- a/common/libs/device_config/host_device_config.cpp
+++ b/common/libs/device_config/host_device_config.cpp
@@ -163,8 +163,9 @@
 
     device_display_config->set_width(cuttlefish_display_config.width);
     device_display_config->set_height(cuttlefish_display_config.height);
-    device_display_config->set_dpi(cuttlefish_config.dpi());
-    device_display_config->set_refresh_rate_hz(cuttlefish_config.refresh_rate_hz());
+    device_display_config->set_dpi(cuttlefish_display_config.dpi);
+    device_display_config->set_refresh_rate_hz(
+        cuttlefish_display_config.refresh_rate_hz);
   }
 }
 
diff --git a/common/libs/fs/shared_fd.cpp b/common/libs/fs/shared_fd.cpp
index 8b21f0c..d206028 100644
--- a/common/libs/fs/shared_fd.cpp
+++ b/common/libs/fs/shared_fd.cpp
@@ -444,7 +444,7 @@
   return rval;
 }
 
-SharedFD SharedFD::VsockServer(unsigned int port, int type) {
+SharedFD SharedFD::VsockServer(unsigned int port, int type, unsigned int cid) {
   auto vsock = SharedFD::Socket(AF_VSOCK, type, 0);
   if (!vsock->IsOpen()) {
     return vsock;
@@ -452,15 +452,17 @@
   sockaddr_vm addr{};
   addr.svm_family = AF_VSOCK;
   addr.svm_port = port;
-  addr.svm_cid = VMADDR_CID_ANY;
+  addr.svm_cid = cid;
   auto casted_addr = reinterpret_cast<sockaddr*>(&addr);
   if (vsock->Bind(casted_addr, sizeof(addr)) == -1) {
-    LOG(ERROR) << "Bind failed (" << vsock->StrError() << ")";
+    LOG(ERROR) << "Port " << port << " Bind failed (" << vsock->StrError()
+               << ")";
     return SharedFD::ErrorFD(vsock->GetErrno());
   }
   if (type == SOCK_STREAM || type == SOCK_SEQPACKET) {
     if (vsock->Listen(4) < 0) {
-      LOG(ERROR) << "Listen failed (" << vsock->StrError() << ")";
+      LOG(ERROR) << "Port" << port << " Listen failed (" << vsock->StrError()
+                 << ")";
       return SharedFD::ErrorFD(vsock->GetErrno());
     }
   }
@@ -511,4 +513,229 @@
   }
 }
 
+/* static */ std::shared_ptr<FileInstance> FileInstance::ClosedInstance() {
+  return std::shared_ptr<FileInstance>(new FileInstance(-1, EBADF));
+}
+
+int FileInstance::Bind(const struct sockaddr* addr, socklen_t addrlen) {
+  errno = 0;
+  int rval = bind(fd_, addr, addrlen);
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::Connect(const struct sockaddr* addr, socklen_t addrlen) {
+  errno = 0;
+  int rval = connect(fd_, addr, addrlen);
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::UNMANAGED_Dup() {
+  errno = 0;
+  int rval = TEMP_FAILURE_RETRY(dup(fd_));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::UNMANAGED_Dup2(int newfd) {
+  errno = 0;
+  int rval = TEMP_FAILURE_RETRY(dup2(fd_, newfd));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::Fcntl(int command, int value) {
+  errno = 0;
+  int rval = TEMP_FAILURE_RETRY(fcntl(fd_, command, value));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::GetSockName(struct sockaddr* addr, socklen_t* addrlen) {
+  errno = 0;
+  int rval = TEMP_FAILURE_RETRY(getsockname(fd_, addr, addrlen));
+  if (rval == -1) {
+    errno_ = errno;
+  }
+  return rval;
+}
+
+unsigned int FileInstance::VsockServerPort() {
+  struct sockaddr_vm vm_socket;
+  socklen_t length = sizeof(vm_socket);
+  GetSockName(reinterpret_cast<struct sockaddr*>(&vm_socket), &length);
+  return vm_socket.svm_port;
+}
+
+int FileInstance::Ioctl(int request, void* val) {
+  errno = 0;
+  int rval = TEMP_FAILURE_RETRY(ioctl(fd_, request, val));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::LinkAtCwd(const std::string& path) {
+  std::string name = "/proc/self/fd/";
+  name += std::to_string(fd_);
+  errno = 0;
+  int rval =
+      linkat(-1, name.c_str(), AT_FDCWD, path.c_str(), AT_SYMLINK_FOLLOW);
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::Listen(int backlog) {
+  errno = 0;
+  int rval = listen(fd_, backlog);
+  errno_ = errno;
+  return rval;
+}
+
+off_t FileInstance::LSeek(off_t offset, int whence) {
+  errno = 0;
+  off_t rval = TEMP_FAILURE_RETRY(lseek(fd_, offset, whence));
+  errno_ = errno;
+  return rval;
+}
+
+ssize_t FileInstance::Recv(void* buf, size_t len, int flags) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(recv(fd_, buf, len, flags));
+  errno_ = errno;
+  return rval;
+}
+
+ssize_t FileInstance::RecvMsg(struct msghdr* msg, int flags) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(recvmsg(fd_, msg, flags));
+  errno_ = errno;
+  return rval;
+}
+
+ssize_t FileInstance::Read(void* buf, size_t count) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(read(fd_, buf, count));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::EventfdRead(eventfd_t* value) {
+  errno = 0;
+  auto rval = eventfd_read(fd_, value);
+  errno_ = errno;
+  return rval;
+}
+
+ssize_t FileInstance::Send(const void* buf, size_t len, int flags) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(send(fd_, buf, len, flags));
+  errno_ = errno;
+  return rval;
+}
+
+ssize_t FileInstance::SendMsg(const struct msghdr* msg, int flags) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(sendmsg(fd_, msg, flags));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::Shutdown(int how) {
+  errno = 0;
+  int rval = shutdown(fd_, how);
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::SetSockOpt(int level, int optname, const void* optval,
+                             socklen_t optlen) {
+  errno = 0;
+  int rval = setsockopt(fd_, level, optname, optval, optlen);
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::GetSockOpt(int level, int optname, void* optval,
+                             socklen_t* optlen) {
+  errno = 0;
+  int rval = getsockopt(fd_, level, optname, optval, optlen);
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::SetTerminalRaw() {
+  errno = 0;
+  termios terminal_settings;
+  int rval = tcgetattr(fd_, &terminal_settings);
+  errno_ = errno;
+  if (rval < 0) {
+    return rval;
+  }
+  cfmakeraw(&terminal_settings);
+  rval = tcsetattr(fd_, TCSANOW, &terminal_settings);
+  errno_ = errno;
+  return rval;
+}
+
+std::string FileInstance::StrError() const {
+  errno = 0;
+  return std::string(strerror(errno_));
+}
+
+ScopedMMap FileInstance::MMap(void* addr, size_t length, int prot, int flags,
+                              off_t offset) {
+  errno = 0;
+  auto ptr = mmap(addr, length, prot, flags, fd_, offset);
+  errno_ = errno;
+  return ScopedMMap(ptr, length);
+}
+
+ssize_t FileInstance::Truncate(off_t length) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(ftruncate(fd_, length));
+  errno_ = errno;
+  return rval;
+}
+
+ssize_t FileInstance::Write(const void* buf, size_t count) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(write(fd_, buf, count));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::EventfdWrite(eventfd_t value) {
+  errno = 0;
+  int rval = eventfd_write(fd_, value);
+  errno_ = errno;
+  return rval;
+}
+
+bool FileInstance::IsATTY() {
+  errno = 0;
+  int rval = isatty(fd_);
+  errno_ = errno;
+  return rval;
+}
+
+FileInstance::FileInstance(int fd, int in_errno) : fd_(fd), errno_(in_errno) {
+  // Ensure every file descriptor managed by a FileInstance has the CLOEXEC
+  // flag
+  TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD, FD_CLOEXEC));
+  std::stringstream identity;
+  identity << "fd=" << fd << " @" << this;
+  identity_ = identity.str();
+}
+
+FileInstance* FileInstance::Accept(struct sockaddr* addr,
+                                   socklen_t* addrlen) const {
+  int fd = TEMP_FAILURE_RETRY(accept(fd_, addr, addrlen));
+  if (fd == -1) {
+    return new FileInstance(fd, errno);
+  } else {
+    return new FileInstance(fd, 0);
+  }
+}
+
 }  // namespace cuttlefish
diff --git a/common/libs/fs/shared_fd.h b/common/libs/fs/shared_fd.h
index e712ab9..3520a92 100644
--- a/common/libs/fs/shared_fd.h
+++ b/common/libs/fs/shared_fd.h
@@ -141,7 +141,8 @@
   static SharedFD SocketLocalServer(const std::string& name, bool is_abstract,
                                     int in_type, mode_t mode);
   static SharedFD SocketLocalServer(int port, int type);
-  static SharedFD VsockServer(unsigned int port, int type);
+  static SharedFD VsockServer(unsigned int port, int type,
+                              unsigned int cid = VMADDR_CID_ANY);
   static SharedFD VsockServer(int type);
   static SharedFD VsockClient(unsigned int cid, unsigned int port, int type);
 
@@ -231,27 +232,12 @@
   virtual ~FileInstance() { Close(); }
 
   // This can't be a singleton because our shared_ptr's aren't thread safe.
-  static std::shared_ptr<FileInstance> ClosedInstance() {
-    return std::shared_ptr<FileInstance>(new FileInstance(-1, EBADF));
-  }
+  static std::shared_ptr<FileInstance> ClosedInstance();
 
-  int Bind(const struct sockaddr* addr, socklen_t addrlen) {
-    errno = 0;
-    int rval = bind(fd_, addr, addrlen);
-    errno_ = errno;
-    return rval;
-  }
-
-  int Connect(const struct sockaddr* addr, socklen_t addrlen) {
-    errno = 0;
-    int rval = connect(fd_, addr, addrlen);
-    errno_ = errno;
-    return rval;
-  }
-
+  int Bind(const struct sockaddr* addr, socklen_t addrlen);
+  int Connect(const struct sockaddr* addr, socklen_t addrlen);
   int ConnectWithTimeout(const struct sockaddr* addr, socklen_t addrlen,
                          struct timeval* timeout);
-
   void Close();
 
   // Returns true if the entire input was copied.
@@ -260,52 +246,16 @@
   // reference type.
   bool CopyFrom(FileInstance& in, size_t length);
 
-  int UNMANAGED_Dup() {
-    errno = 0;
-    int rval = TEMP_FAILURE_RETRY(dup(fd_));
-    errno_ = errno;
-    return rval;
-  }
-
-  int UNMANAGED_Dup2(int newfd) {
-    errno = 0;
-    int rval = TEMP_FAILURE_RETRY(dup2(fd_, newfd));
-    errno_ = errno;
-    return rval;
-  }
-
-  int Fcntl(int command, int value) {
-    errno = 0;
-    int rval = TEMP_FAILURE_RETRY(fcntl(fd_, command, value));
-    errno_ = errno;
-    return rval;
-  }
+  int UNMANAGED_Dup();
+  int UNMANAGED_Dup2(int newfd);
+  int Fcntl(int command, int value);
 
   int GetErrno() const { return errno_; }
+  int GetSockName(struct sockaddr* addr, socklen_t* addrlen);
 
-  int GetSockName(struct sockaddr* addr, socklen_t* addrlen) {
-    errno = 0;
-    int rval = TEMP_FAILURE_RETRY(getsockname(fd_, addr, addrlen));
-    if (rval == -1) {
-      errno_ = errno;
-    }
-    return rval;
-  }
+  unsigned int VsockServerPort();
 
-  unsigned int VsockServerPort() {
-    struct sockaddr_vm vm_socket;
-    socklen_t length = sizeof(vm_socket);
-    GetSockName(reinterpret_cast<struct sockaddr*>(&vm_socket), &length);
-    return vm_socket.svm_port;
-  }
-
-  int Ioctl(int request, void* val = nullptr) {
-    errno = 0;
-    int rval = TEMP_FAILURE_RETRY(ioctl(fd_, request, val));
-    errno_ = errno;
-    return rval;
-  }
-
+  int Ioctl(int request, void* val = nullptr);
   bool IsOpen() const { return fd_ != -1; }
 
   // in probably isn't modified, but the API spec doesn't have const.
@@ -320,73 +270,16 @@
    * Using this on a file opened with O_TMPFILE can link it into the filesystem.
    */
   // Used with O_TMPFILE files to attach them to the filesystem.
-  int LinkAtCwd(const std::string& path) {
-    std::string name = "/proc/self/fd/";
-    name += std::to_string(fd_);
-    errno = 0;
-    int rval = linkat(
-        -1, name.c_str(), AT_FDCWD, path.c_str(), AT_SYMLINK_FOLLOW);
-    errno_ = errno;
-    return rval;
-  }
-
-  int Listen(int backlog) {
-    errno = 0;
-    int rval = listen(fd_, backlog);
-    errno_ = errno;
-    return rval;
-  }
-
+  int LinkAtCwd(const std::string& path);
+  int Listen(int backlog);
   static void Log(const char* message);
-
-  off_t LSeek(off_t offset, int whence) {
-    errno = 0;
-    off_t rval = TEMP_FAILURE_RETRY(lseek(fd_, offset, whence));
-    errno_ = errno;
-    return rval;
-  }
-
-  ssize_t Recv(void* buf, size_t len, int flags) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(recv(fd_, buf, len, flags));
-    errno_ = errno;
-    return rval;
-  }
-
-  ssize_t RecvMsg(struct msghdr* msg, int flags) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(recvmsg(fd_, msg, flags));
-    errno_ = errno;
-    return rval;
-  }
-
-  ssize_t Read(void* buf, size_t count) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(read(fd_, buf, count));
-    errno_ = errno;
-    return rval;
-  }
-
-  int EventfdRead(eventfd_t* value) {
-    errno = 0;
-    auto rval = eventfd_read(fd_, value);
-    errno_ = errno;
-    return rval;
-  }
-
-  ssize_t Send(const void* buf, size_t len, int flags) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(send(fd_, buf, len, flags));
-    errno_ = errno;
-    return rval;
-  }
-
-  ssize_t SendMsg(const struct msghdr* msg, int flags) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(sendmsg(fd_, msg, flags));
-    errno_ = errno;
-    return rval;
-  }
+  off_t LSeek(off_t offset, int whence);
+  ssize_t Recv(void* buf, size_t len, int flags);
+  ssize_t RecvMsg(struct msghdr* msg, int flags);
+  ssize_t Read(void* buf, size_t count);
+  int EventfdRead(eventfd_t* value);
+  ssize_t Send(const void* buf, size_t len, int flags);
+  ssize_t SendMsg(const struct msghdr* msg, int flags);
 
   template <typename... Args>
   ssize_t SendFileDescriptors(const void* buf, size_t len, Args&&... sent_fds) {
@@ -398,118 +291,25 @@
     return ret;
   }
 
-  int Shutdown(int how) {
-    errno = 0;
-    int rval = shutdown(fd_, how);
-    errno_ = errno;
-    return rval;
-  }
-
+  int Shutdown(int how);
   void Set(fd_set* dest, int* max_index) const;
-
-  int SetSockOpt(int level, int optname, const void* optval, socklen_t optlen) {
-    errno = 0;
-    int rval = setsockopt(fd_, level, optname, optval, optlen);
-    errno_ = errno;
-    return rval;
-  }
-
-  int GetSockOpt(int level, int optname, void* optval, socklen_t* optlen) {
-    errno = 0;
-    int rval = getsockopt(fd_, level, optname, optval, optlen);
-    errno_ = errno;
-    return rval;
-  }
-
-  int SetTerminalRaw() {
-    errno = 0;
-    termios terminal_settings;
-    int rval = tcgetattr(fd_, &terminal_settings);
-    errno_ = errno;
-    if (rval < 0) {
-      return rval;
-    }
-    cfmakeraw(&terminal_settings);
-    rval = tcsetattr(fd_, TCSANOW, &terminal_settings);
-    errno_ = errno;
-    return rval;
-  }
-
-  const char* StrError() const {
-    errno = 0;
-    FileInstance* s = const_cast<FileInstance*>(this);
-    char* out = strerror_r(errno_, s->strerror_buf_, sizeof(strerror_buf_));
-
-    // From man page:
-    //  strerror_r() returns a pointer to a string containing the error message.
-    //  This may be either a pointer to a string that the function stores in
-    //  buf, or a pointer to some (immutable) static string (in which case buf
-    //  is unused).
-    if (out != s->strerror_buf_) {
-      strncpy(s->strerror_buf_, out, sizeof(strerror_buf_));
-    }
-    return strerror_buf_;
-  }
-
-  ScopedMMap MMap(void* addr, size_t length, int prot, int flags,
-                  off_t offset) {
-    errno = 0;
-    auto ptr = mmap(addr, length, prot, flags, fd_, offset);
-    errno_ = errno;
-    return ScopedMMap(ptr, length);
-  }
-
-  ssize_t Truncate(off_t length) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(ftruncate(fd_, length));
-    errno_ = errno;
-    return rval;
-  }
-
-  ssize_t Write(const void* buf, size_t count) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(write(fd_, buf, count));
-    errno_ = errno;
-    return rval;
-  }
-
-  int EventfdWrite(eventfd_t value) {
-    errno = 0;
-    int rval = eventfd_write(fd_, value);
-    errno_ = errno;
-    return rval;
-  }
-
-  bool IsATTY() {
-    errno = 0;
-    int rval = isatty(fd_);
-    errno_ = errno;
-    return rval;
-  }
+  int SetSockOpt(int level, int optname, const void* optval, socklen_t optlen);
+  int GetSockOpt(int level, int optname, void* optval, socklen_t* optlen);
+  int SetTerminalRaw();
+  std::string StrError() const;
+  ScopedMMap MMap(void* addr, size_t length, int prot, int flags, off_t offset);
+  ssize_t Truncate(off_t length);
+  ssize_t Write(const void* buf, size_t count);
+  int EventfdWrite(eventfd_t value);
+  bool IsATTY();
 
  private:
-  FileInstance(int fd, int in_errno) : fd_(fd), errno_(in_errno) {
-    // Ensure every file descriptor managed by a FileInstance has the CLOEXEC
-    // flag
-    TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD, FD_CLOEXEC));
-    std::stringstream identity;
-    identity << "fd=" << fd << " @" << this;
-    identity_ = identity.str();
-  }
-
-  FileInstance* Accept(struct sockaddr* addr, socklen_t* addrlen) const {
-    int fd = TEMP_FAILURE_RETRY(accept(fd_, addr, addrlen));
-    if (fd == -1) {
-      return new FileInstance(fd, errno);
-    } else {
-      return new FileInstance(fd, 0);
-    }
-  }
+  FileInstance(int fd, int in_errno);
+  FileInstance* Accept(struct sockaddr* addr, socklen_t* addrlen) const;
 
   int fd_;
   int errno_;
   std::string identity_;
-  char strerror_buf_[160];
 };
 
 /* Methods that need both a fully defined SharedFD and a fully defined
diff --git a/common/libs/time/monotonic_time.h b/common/libs/time/monotonic_time.h
deleted file mode 100644
index 2839001..0000000
--- a/common/libs/time/monotonic_time.h
+++ /dev/null
@@ -1,281 +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.
- */
-#pragma once
-
-#include <stdint.h>
-#include <time.h>
-
-namespace cuttlefish {
-namespace time {
-
-static const int64_t kNanosecondsPerSecond = 1000000000;
-
-class TimeDifference {
- public:
-  TimeDifference(time_t seconds, long nanoseconds, int64_t scale) :
-      scale_(scale), truncated_(false) {
-    ts_.tv_sec = seconds;
-    ts_.tv_nsec = nanoseconds;
-    if (scale_ == kNanosecondsPerSecond) {
-      truncated_ = true;
-      truncated_ns_ = 0;
-    }
-  }
-
-  TimeDifference(const TimeDifference& in, int64_t scale) :
-      scale_(scale), truncated_(false) {
-    ts_ = in.GetTS();
-    if (scale_ == kNanosecondsPerSecond) {
-      truncated_ = true;
-      truncated_ns_ = 0;
-    } else if ((in.scale_ % scale_) == 0) {
-      truncated_ = true;
-      truncated_ns_ = ts_.tv_nsec;
-    }
-  }
-
-  TimeDifference(const struct timespec& in, int64_t scale) :
-      ts_(in), scale_(scale), truncated_(false) { }
-
-  TimeDifference operator*(const uint32_t factor) {
-    TimeDifference rval = *this;
-    rval.ts_.tv_sec = ts_.tv_sec * factor;
-    // Create temporary variable to hold the multiplied
-    // nanoseconds so that no overflow is possible.
-    // Nanoseconds must be in [0, 10^9) and so all are less
-    // then 2^30. Even multiplied by the largest uint32
-    // this will fit in a 64-bit int without overflow.
-    int64_t tv_nsec = static_cast<int64_t>(ts_.tv_nsec) * factor;
-    rval.ts_.tv_sec += (tv_nsec / kNanosecondsPerSecond);
-    rval.ts_.tv_nsec = tv_nsec % kNanosecondsPerSecond;
-    return rval;
-  }
-
-  TimeDifference operator+(const TimeDifference& other) const {
-    struct timespec ret = ts_;
-    ret.tv_nsec = (ts_.tv_nsec + other.ts_.tv_nsec) % 1000000000;
-    ret.tv_sec = (ts_.tv_sec + other.ts_.tv_sec) +
-                  (ts_.tv_nsec + other.ts_.tv_nsec) / 1000000000;
-    return TimeDifference(ret, scale_ < other.scale_ ? scale_: other.scale_);
-  }
-
-  TimeDifference operator-(const TimeDifference& other) const {
-    struct timespec ret = ts_;
-    // Keeps nanoseconds positive and allow negative numbers only on
-    // seconds.
-    ret.tv_nsec = (1000000000 + ts_.tv_nsec - other.ts_.tv_nsec) % 1000000000;
-    ret.tv_sec = (ts_.tv_sec - other.ts_.tv_sec) -
-                  (ts_.tv_nsec < other.ts_.tv_nsec ? 1 : 0);
-    return TimeDifference(ret, scale_ < other.scale_ ? scale_: other.scale_);
-  }
-
-  bool operator<(const TimeDifference& other) const {
-    return ts_.tv_sec < other.ts_.tv_sec ||
-           (ts_.tv_sec == other.ts_.tv_sec && ts_.tv_nsec < other.ts_.tv_nsec);
-  }
-
-  int64_t count() const {
-    return ts_.tv_sec * (kNanosecondsPerSecond / scale_) + ts_.tv_nsec / scale_;
-  }
-
-  time_t seconds() const {
-    return ts_.tv_sec;
-  }
-
-  long subseconds_in_ns() const {
-    if (!truncated_) {
-      truncated_ns_ = (ts_.tv_nsec / scale_) * scale_;
-      truncated_ = true;
-    }
-    return truncated_ns_;
-  }
-
-  struct timespec GetTS() const {
-    // We can't assume C++11, so avoid extended initializer lists.
-    struct timespec rval = { ts_.tv_sec, subseconds_in_ns()};
-    return rval;
-  }
-
- protected:
-  struct timespec ts_;
-  int64_t scale_;
-  mutable bool truncated_;
-  mutable long truncated_ns_;
-};
-
-class MonotonicTimePoint {
- public:
-  static MonotonicTimePoint Now() {
-    struct timespec ts;
-#ifdef CLOCK_MONOTONIC_RAW
-    // WARNING:
-    // While we do have CLOCK_MONOTONIC_RAW, we can't depend on it until:
-    // - ALL places relying on MonotonicTimePoint are fixed,
-    // - pthread supports pthread_timewait_monotonic.
-    //
-    // This is currently observable as a LEGITIMATE problem while running
-    // pthread_test. DO NOT revert this to CLOCK_MONOTONIC_RAW until test
-    // passes.
-    clock_gettime(CLOCK_MONOTONIC, &ts);
-#else
-    clock_gettime(CLOCK_MONOTONIC, &ts);
-#endif
-    return MonotonicTimePoint(ts);
-  }
-
-  MonotonicTimePoint() {
-    ts_.tv_sec = 0;
-    ts_.tv_nsec = 0;
-  }
-
-  explicit MonotonicTimePoint(const struct timespec& ts) {
-    ts_ = ts;
-  }
-
-  TimeDifference SinceEpoch() const {
-    return TimeDifference(ts_, 1);
-  }
-
-  TimeDifference operator-(const MonotonicTimePoint& other) const {
-    struct timespec rval;
-    rval.tv_sec = ts_.tv_sec - other.ts_.tv_sec;
-    rval.tv_nsec = ts_.tv_nsec - other.ts_.tv_nsec;
-    if (rval.tv_nsec < 0) {
-      --rval.tv_sec;
-      rval.tv_nsec += kNanosecondsPerSecond;
-    }
-    return TimeDifference(rval, 1);
-  }
-
-  MonotonicTimePoint operator+(const TimeDifference& other) const {
-    MonotonicTimePoint rval = *this;
-    rval.ts_.tv_sec += other.seconds();
-    rval.ts_.tv_nsec += other.subseconds_in_ns();
-    if (rval.ts_.tv_nsec >= kNanosecondsPerSecond) {
-      ++rval.ts_.tv_sec;
-      rval.ts_.tv_nsec -= kNanosecondsPerSecond;
-    }
-    return rval;
-  }
-
-  bool operator==(const MonotonicTimePoint& other) const {
-    return (ts_.tv_sec == other.ts_.tv_sec) &&
-        (ts_.tv_nsec == other.ts_.tv_nsec);
-  }
-
-  bool operator!=(const MonotonicTimePoint& other) const {
-    return !(*this == other);
-  }
-
-  bool operator<(const MonotonicTimePoint& other) const {
-    return ((ts_.tv_sec - other.ts_.tv_sec) < 0) ||
-        ((ts_.tv_sec == other.ts_.tv_sec) &&
-         (ts_.tv_nsec < other.ts_.tv_nsec));
-  }
-
-  bool operator>(const MonotonicTimePoint& other) const {
-    return other < *this;
-  }
-
-  bool operator<=(const MonotonicTimePoint& other) const {
-    return !(*this > other);
-  }
-
-  bool operator>=(const MonotonicTimePoint& other) const {
-    return !(*this < other);
-  }
-
-  MonotonicTimePoint& operator+=(const TimeDifference& other) {
-    ts_.tv_sec += other.seconds();
-    ts_.tv_nsec += other.subseconds_in_ns();
-    if (ts_.tv_nsec >= kNanosecondsPerSecond) {
-      ++ts_.tv_sec;
-      ts_.tv_nsec -= kNanosecondsPerSecond;
-    }
-    return *this;
-  }
-
-  MonotonicTimePoint& operator-=(const TimeDifference& other) {
-    ts_.tv_sec -= other.seconds();
-    ts_.tv_nsec -= other.subseconds_in_ns();
-    if (ts_.tv_nsec < 0) {
-      --ts_.tv_sec;
-      ts_.tv_nsec += kNanosecondsPerSecond;
-    }
-    return *this;
-  }
-
-  void ToTimespec(struct timespec* dest) const {
-    *dest = ts_;
-  }
-
- protected:
-  struct timespec ts_;
-};
-
-class Seconds : public TimeDifference {
- public:
-  explicit Seconds(const TimeDifference& difference) :
-      TimeDifference(difference, kNanosecondsPerSecond) { }
-
-  Seconds(int64_t seconds) :
-      TimeDifference(seconds, 0, kNanosecondsPerSecond) { }
-};
-
-class Milliseconds : public TimeDifference {
- public:
-  explicit Milliseconds(const TimeDifference& difference) :
-      TimeDifference(difference, kScale) { }
-
-  Milliseconds(int64_t ms) : TimeDifference(
-      ms / 1000, (ms % 1000) * kScale, kScale) { }
-
- protected:
-  static const int kScale = kNanosecondsPerSecond / 1000;
-};
-
-class Microseconds : public TimeDifference {
- public:
-  explicit Microseconds(const TimeDifference& difference) :
-      TimeDifference(difference, kScale) { }
-
-  Microseconds(int64_t micros) : TimeDifference(
-      micros / 1000000, (micros % 1000000) * kScale, kScale) { }
-
- protected:
-  static const int kScale = kNanosecondsPerSecond / 1000000;
-};
-
-class Nanoseconds : public TimeDifference {
- public:
-  explicit Nanoseconds(const TimeDifference& difference) :
-      TimeDifference(difference, 1) { }
-  Nanoseconds(int64_t ns) : TimeDifference(ns / kNanosecondsPerSecond,
-                                           ns % kNanosecondsPerSecond, 1) { }
-};
-
-}  // namespace time
-}  // namespace cuttlefish
-
-/**
- * Legacy support for microseconds. Use MonotonicTimePoint in new code.
- */
-static const int64_t kSecsToUsecs = static_cast<int64_t>(1000) * 1000;
-
-static inline int64_t get_monotonic_usecs() {
-  return cuttlefish::time::Microseconds(
-      cuttlefish::time::MonotonicTimePoint::Now().SinceEpoch()).count();
-}
diff --git a/common/libs/utils/Android.bp b/common/libs/utils/Android.bp
index f8ca5e2..a64bb18 100644
--- a/common/libs/utils/Android.bp
+++ b/common/libs/utils/Android.bp
@@ -23,25 +23,29 @@
         "archive.cpp",
         "subprocess.cpp",
         "environment.cpp",
-        "size_utils.cpp",
+        "flag_parser.cpp",
+        "shared_fd_flag.cpp",
         "files.cpp",
         "users.cpp",
         "network.cpp",
         "base64.cpp",
         "tcp_socket.cpp",
         "tee_logging.cpp",
+        "vsock_connection.cpp",
     ],
     shared: {
         shared_libs: [
             "libbase",
             "libcuttlefish_fs",
             "libcrypto",
+            "libjsoncpp",
         ],
     },
     static: {
         static_libs: [
             "libbase",
             "libcuttlefish_fs",
+            "libjsoncpp",
         ],
         shared_libs: [
           "libcrypto", // libcrypto_static is not accessible from all targets
@@ -49,3 +53,33 @@
     },
     defaults: ["cuttlefish_host"],
 }
+
+cc_test_host {
+    name: "libcuttlefish_utils_test",
+    srcs: [
+        "flag_parser_test.cpp",
+    ],
+    static_libs: [
+        "libbase",
+        "libcuttlefish_fs",
+        "libcuttlefish_utils",
+    ],
+    shared_libs: [
+        "libcrypto",
+        "liblog",
+        "libxml2",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+    defaults: ["cuttlefish_host"],
+}
+
+cc_library {
+    name: "libvsock_utils",
+    srcs: ["vsock_connection.cpp"],
+    shared_libs: ["libbase", "libcuttlefish_fs", "liblog", "libjsoncpp"],
+    defaults: ["cuttlefish_guest_only"],
+    include_dirs: ["device/google/cuttlefish"],
+    export_include_dirs: ["."],
+}
diff --git a/common/libs/utils/flag_parser.cpp b/common/libs/utils/flag_parser.cpp
new file mode 100644
index 0000000..e011de6
--- /dev/null
+++ b/common/libs/utils/flag_parser.cpp
@@ -0,0 +1,444 @@
+/*
+ * 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.
+ */
+
+#include <common/libs/utils/flag_parser.h>
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <unordered_set>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+namespace cuttlefish {
+
+std::ostream& operator<<(std::ostream& out, const FlagAlias& alias) {
+  switch (alias.mode) {
+    case FlagAliasMode::kFlagExact:
+      return out << alias.name;
+    case FlagAliasMode::kFlagPrefix:
+      return out << alias.name << "*";
+    case FlagAliasMode::kFlagConsumesFollowing:
+      return out << alias.name << " *";
+    default:
+      LOG(FATAL) << "Unexpected flag alias mode " << (int)alias.mode;
+  }
+  return out;
+}
+
+Flag& Flag::UnvalidatedAlias(const FlagAlias& alias) & {
+  aliases_.push_back(alias);
+  return *this;
+}
+Flag Flag::UnvalidatedAlias(const FlagAlias& alias) && {
+  aliases_.push_back(alias);
+  return *this;
+}
+
+void Flag::ValidateAlias(const FlagAlias& alias) {
+  using android::base::EndsWith;
+  using android::base::StartsWith;
+
+  CHECK(StartsWith(alias.name, "-")) << "Flags should start with \"-\"";
+  if (alias.mode == FlagAliasMode::kFlagPrefix) {
+    CHECK(EndsWith(alias.name, "=")) << "Prefix flags shold end with \"=\"";
+  }
+
+  CHECK(!HasAlias(alias)) << "Duplicate flag alias: " << alias.name;
+  if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) {
+    CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name}))
+        << "Overlapping flag aliases for " << alias.name;
+  } else if (alias.mode == FlagAliasMode::kFlagExact) {
+    CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name}))
+        << "Overlapping flag aliases for " << alias.name;
+  }
+}
+
+Flag& Flag::Alias(const FlagAlias& alias) & {
+  ValidateAlias(alias);
+  aliases_.push_back(alias);
+  return *this;
+}
+Flag Flag::Alias(const FlagAlias& alias) && {
+  ValidateAlias(alias);
+  aliases_.push_back(alias);
+  return *this;
+}
+
+Flag& Flag::Help(const std::string& help) & {
+  help_ = help;
+  return *this;
+}
+Flag Flag::Help(const std::string& help) && {
+  help_ = help;
+  return *this;
+}
+
+Flag& Flag::Getter(std::function<std::string()> fn) & {
+  getter_ = std::move(fn);
+  return *this;
+}
+Flag Flag::Getter(std::function<std::string()> fn) && {
+  getter_ = std::move(fn);
+  return *this;
+}
+
+Flag& Flag::Setter(std::function<bool(const FlagMatch&)> fn) & {
+  setter_ = std::move(fn);
+  return *this;
+}
+Flag Flag::Setter(std::function<bool(const FlagMatch&)> fn) && {
+  setter_ = std::move(fn);
+  return *this;
+}
+
+Flag::FlagProcessResult Flag::Process(
+    const std::string& arg, const std::optional<std::string>& next_arg) const {
+  if (!setter_ && aliases_.size() > 0) {
+    LOG(ERROR) << "No setter for flag with alias " << aliases_[0].name;
+    return FlagProcessResult::kFlagError;
+  }
+  for (auto& alias : aliases_) {
+    switch (alias.mode) {
+      case FlagAliasMode::kFlagConsumesFollowing:
+        if (arg != alias.name) {
+          continue;
+        }
+        if (!next_arg) {
+          LOG(ERROR) << "Expected an argument after \"" << arg << "\"";
+          return FlagProcessResult::kFlagError;
+        }
+        if (!(*setter_)({arg, *next_arg})) {
+          LOG(ERROR) << "Processing \"" << arg << "\" \"" << *next_arg
+                     << "\" failed";
+          return FlagProcessResult::kFlagError;
+        }
+        return FlagProcessResult::kFlagConsumedWithFollowing;
+      case FlagAliasMode::kFlagExact:
+        if (arg != alias.name) {
+          continue;
+        }
+        if (!(*setter_)({arg, arg})) {
+          LOG(ERROR) << "Processing \"" << arg << "\" failed";
+          return FlagProcessResult::kFlagError;
+        }
+        return FlagProcessResult::kFlagConsumed;
+      case FlagAliasMode::kFlagPrefix:
+        if (!android::base::StartsWith(arg, alias.name)) {
+          continue;
+        }
+        if (!(*setter_)({alias.name, arg.substr(alias.name.size())})) {
+          LOG(ERROR) << "Processing \"" << arg << "\" failed";
+          return FlagProcessResult::kFlagError;
+        }
+        return FlagProcessResult::kFlagConsumed;
+      default:
+        LOG(ERROR) << "Unknown flag alias mode: " << (int)alias.mode;
+        return FlagProcessResult::kFlagError;
+    }
+  }
+  return FlagProcessResult::kFlagSkip;
+}
+
+bool Flag::Parse(std::vector<std::string>& arguments) const {
+  for (int i = 0; i < arguments.size();) {
+    std::string arg = arguments[i];
+    std::optional<std::string> next_arg;
+    if (i < arguments.size() - 1) {
+      next_arg = arguments[i + 1];
+    }
+    auto result = Process(arg, next_arg);
+    if (result == FlagProcessResult::kFlagError) {
+      LOG(ERROR) << "Failure in parsing \"" << arg << "\"";
+      return false;
+    } else if (result == FlagProcessResult::kFlagConsumed) {
+      arguments.erase(arguments.begin() + i);
+    } else if (result == FlagProcessResult::kFlagConsumedWithFollowing) {
+      arguments.erase(arguments.begin() + i, arguments.begin() + i + 2);
+    } else if (result == FlagProcessResult::kFlagSkip) {
+      i++;
+      continue;
+    } else {
+      LOG(ERROR) << "Unknown FlagProcessResult: " << (int)result;
+      return false;
+    }
+  }
+  return true;
+}
+bool Flag::Parse(std::vector<std::string>&& arguments) const {
+  return Parse(static_cast<std::vector<std::string>&>(arguments));
+}
+
+bool Flag::HasAlias(const FlagAlias& test) const {
+  for (const auto& alias : aliases_) {
+    if (alias.mode == test.mode && alias.name == test.name) {
+      return true;
+    }
+  }
+  return false;
+}
+
+static std::string XmlEscape(const std::string& s) {
+  using android::base::StringReplace;
+  return StringReplace(StringReplace(s, "<", "&lt;", true), ">", "&gt;", true);
+}
+
+bool Flag::WriteGflagsCompatXml(std::ostream& out) const {
+  std::unordered_set<std::string> name_guesses;
+  for (const auto& alias : aliases_) {
+    std::string_view name = alias.name;
+    if (!android::base::ConsumePrefix(&name, "-")) {
+      continue;
+    }
+    android::base::ConsumePrefix(&name, "-");
+    if (alias.mode == FlagAliasMode::kFlagExact) {
+      android::base::ConsumePrefix(&name, "no");
+      name_guesses.insert(std::string{name});
+    } else if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) {
+      name_guesses.insert(std::string{name});
+    } else if (alias.mode == FlagAliasMode::kFlagPrefix) {
+      if (!android::base::ConsumeSuffix(&name, "=")) {
+        continue;
+      }
+      name_guesses.insert(std::string{name});
+    }
+  }
+  bool found_alias = false;
+  for (const auto& name : name_guesses) {
+    bool has_bool_aliases =
+        HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) &&
+        HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) &&
+        HasAlias({FlagAliasMode::kFlagExact, "-" + name}) &&
+        HasAlias({FlagAliasMode::kFlagExact, "--" + name}) &&
+        HasAlias({FlagAliasMode::kFlagExact, "-no" + name}) &&
+        HasAlias({FlagAliasMode::kFlagExact, "--no" + name});
+    bool has_other_aliases =
+        HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) &&
+        HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) &&
+        HasAlias({FlagAliasMode::kFlagConsumesFollowing, "-" + name}) &&
+        HasAlias({FlagAliasMode::kFlagConsumesFollowing, "--" + name});
+    if (has_bool_aliases && has_other_aliases) {
+      LOG(ERROR) << "Expected exactly one of has_bool_aliases and "
+                 << "has_other_aliases, got both for \"" << name << "\".";
+      return false;
+    } else if (!has_bool_aliases && !has_other_aliases) {
+      continue;
+    }
+    found_alias = true;
+    // Lifted from external/gflags/src/gflags_reporting.cc:DescribeOneFlagInXML
+    out << "<flag>\n";
+    out << "  <file>file.cc</file>\n";
+    out << "  <name>" << XmlEscape(name) << "</name>\n";
+    auto help = help_ ? XmlEscape(*help_) : std::string{""};
+    out << "  <meaning>" << help << "</meaning>\n";
+    auto value = getter_ ? XmlEscape((*getter_)()) : std::string{""};
+    out << "  <default>" << value << "</default>\n";
+    out << "  <current>" << value << "</current>\n";
+    out << "  <type>" << (has_bool_aliases ? "bool" : "string") << "</type>\n";
+    out << "</flag>\n";
+  }
+  return found_alias;
+}
+
+std::ostream& operator<<(std::ostream& out, const Flag& flag) {
+  out << "[";
+  for (auto it = flag.aliases_.begin(); it != flag.aliases_.end(); it++) {
+    if (it != flag.aliases_.begin()) {
+      out << ", ";
+    }
+    out << *it;
+  }
+  out << "]\n";
+  if (flag.help_) {
+    out << "(" << *flag.help_ << ")\n";
+  }
+  if (flag.getter_) {
+    out << "(Current value: \"" << (*flag.getter_)() << "\")\n";
+  }
+  return out;
+}
+
+std::vector<std::string> ArgsToVec(int argc, char** argv) {
+  std::vector<std::string> args;
+  for (int i = 0; i < argc; i++) {
+    args.push_back(argv[i]);
+  }
+  return args;
+}
+
+bool ParseFlags(const std::vector<Flag>& flags,
+                std::vector<std::string>& args) {
+  for (const auto& flag : flags) {
+    if (!flag.Parse(args)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ParseFlags(const std::vector<Flag>& flags,
+                std::vector<std::string>&& args) {
+  for (const auto& flag : flags) {
+    if (!flag.Parse(args)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool WriteGflagsCompatXml(const std::vector<Flag>& flags, std::ostream& out) {
+  for (const auto& flag : flags) {
+    if (!flag.WriteGflagsCompatXml(out)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+Flag HelpFlag(const std::vector<Flag>& flags, const std::string& text) {
+  auto setter = [&](FlagMatch) {
+    if (text.size() > 0) {
+      LOG(INFO) << text;
+    }
+    for (const auto& flag : flags) {
+      LOG(INFO) << flag;
+    }
+    return false;
+  };
+  return Flag()
+      .Alias({FlagAliasMode::kFlagExact, "-help"})
+      .Alias({FlagAliasMode::kFlagExact, "--help"})
+      .Setter(setter);
+}
+
+Flag InvalidFlagGuard() {
+  return Flag()
+      .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, "-"})
+      .Help(
+          "This executable only supports the flags in `-help`. Positional "
+          "arguments may be supported.")
+      .Setter([](const FlagMatch& match) {
+        LOG(ERROR) << "Unknown flag " << match.value;
+        return false;
+      });
+}
+
+Flag UnexpectedArgumentGuard() {
+  return Flag()
+      .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, ""})
+      .Help(
+          "This executable only supports the flags in `-help`. Positional "
+          "arguments are not supported.")
+      .Setter([](const FlagMatch& match) {
+        LOG(ERROR) << "Unexpected argument \"" << match.value << "\"";
+        return false;
+      });
+}
+
+Flag GflagsCompatFlag(const std::string& name) {
+  return Flag()
+      .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="})
+      .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="})
+      .Alias({FlagAliasMode::kFlagConsumesFollowing, "-" + name})
+      .Alias({FlagAliasMode::kFlagConsumesFollowing, "--" + name});
+};
+
+Flag GflagsCompatFlag(const std::string& name, std::string& value) {
+  return GflagsCompatFlag(name)
+      .Getter([&value]() { return value; })
+      .Setter([&value](const FlagMatch& match) {
+        value = match.value;
+        return true;
+      });
+}
+
+template <typename T>
+std::optional<T> ParseInteger(const std::string& value) {
+  if (value.size() == 0) {
+    return {};
+  }
+  const char* base = value.c_str();
+  char* end = nullptr;
+  errno = 0;
+  auto r = strtoll(base, &end, /* auto-detect */ 0);
+  if (errno != 0 || end != base + value.size()) {
+    return {};
+  }
+  if (static_cast<T>(r) != r) {
+    return {};
+  }
+  return r;
+}
+
+template <typename T>
+static Flag GflagsCompatNumericFlagGeneric(const std::string& name, T& value) {
+  return GflagsCompatFlag(name)
+      .Getter([&value]() { return std::to_string(value); })
+      .Setter([&value](const FlagMatch& match) {
+        auto parsed = ParseInteger<T>(match.value);
+        if (parsed) {
+          value = *parsed;
+          return true;
+        } else {
+          LOG(ERROR) << "Failed to parse \"" << match.value
+                     << "\" as an integer";
+          return false;
+        }
+      });
+}
+
+Flag GflagsCompatFlag(const std::string& name, int32_t& value) {
+  return GflagsCompatNumericFlagGeneric(name, value);
+}
+
+Flag GflagsCompatFlag(const std::string& name, bool& value) {
+  return Flag()
+      .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="})
+      .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="})
+      .Alias({FlagAliasMode::kFlagExact, "-" + name})
+      .Alias({FlagAliasMode::kFlagExact, "--" + name})
+      .Alias({FlagAliasMode::kFlagExact, "-no" + name})
+      .Alias({FlagAliasMode::kFlagExact, "--no" + name})
+      .Getter([&value]() { return value ? "true" : "false"; })
+      .Setter([name, &value](const FlagMatch& match) {
+        const auto& key = match.key;
+        if (key == "-" + name || key == "--" + name) {
+          value = true;
+          return true;
+        } else if (key == "-no" + name || key == "--no" + name) {
+          value = false;
+          return true;
+        } else if (key == "-" + name + "=" || key == "--" + name + "=") {
+          if (match.value == "true") {
+            value = true;
+            return true;
+          } else if (match.value == "false") {
+            value = false;
+            return true;
+          } else {
+            LOG(ERROR) << "Unexpected boolean value \"" << match.value << "\""
+                       << " for \"" << name << "\"";
+            return false;
+          }
+        }
+        LOG(ERROR) << "Unexpected key \"" << match.key << "\""
+                   << " for \"" << name << "\"";
+        return false;
+      });
+};
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/flag_parser.h b/common/libs/utils/flag_parser.h
new file mode 100644
index 0000000..9f66ded
--- /dev/null
+++ b/common/libs/utils/flag_parser.h
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <functional>
+#include <optional>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+
+/* Support for parsing individual flags out of a larger list of flags. This
+ * supports externally determining the order that flags are evaluated in, and
+ * incrementally integrating with existing flag parsing implementations.
+ *
+ * Start with Flag() or one of the GflagsCompatFlag(...) functions to create new
+ * flags. These flags should be aggregated through the application through some
+ * other mechanism and then evaluated individually with Flag::Parse or together
+ * with ParseFlags on arguments. */
+
+namespace cuttlefish {
+
+/* The matching behavior used with the name in `FlagAlias::name`. */
+enum class FlagAliasMode {
+  /* Match arguments of the form `<name><value>`. In practice, <name> usually
+   * looks like "-flag=" or "--flag=", where the "-" and "=" are included in
+   * parsing. */
+  kFlagPrefix,
+  /* Match arguments of the form `<name>`. In practice, <name> will look like
+   * "-flag" or "--flag". */
+  kFlagExact,
+  /* Match a pair of arguments of the form `<name>` `<value>`. In practice,
+   * <name> will look like "-flag" or "--flag". */
+  kFlagConsumesFollowing,
+};
+
+/* A single matching rule for a `Flag`. One `Flag` can have multiple rules. */
+struct FlagAlias {
+  FlagAliasMode mode;
+  std::string name;
+};
+
+std::ostream& operator<<(std::ostream&, const FlagAlias&);
+
+/* A successful match in an argument list from a `FlagAlias` inside a `Flag`.
+ * The `key` value corresponds to `FlagAlias::name`. For a match of
+ * `FlagAliasMode::kFlagExact`, `key` and `value` will both be the `name`. */
+struct FlagMatch {
+  std::string key;
+  std::string value;
+};
+
+class Flag {
+ public:
+  /* Add an alias that triggers matches and calls to the `Setter` function. */
+  Flag& Alias(const FlagAlias& alias) &;
+  Flag Alias(const FlagAlias& alias) &&;
+  /* Set help text, visible in the class ostream writer method. Optional. */
+  Flag& Help(const std::string&) &;
+  Flag Help(const std::string&) &&;
+  /* Set a loader that displays the current value in help text. Optional. */
+  Flag& Getter(std::function<std::string()>) &;
+  Flag Getter(std::function<std::string()>) &&;
+  /* Set the callback for matches. The callback be invoked multiple times. */
+  Flag& Setter(std::function<bool(const FlagMatch&)>) &;
+  Flag Setter(std::function<bool(const FlagMatch&)>) &&;
+
+  /* Examines a list of arguments, removing any matches from the list and
+   * invoking the `Setter` for every match. Returns `false` if the callback ever
+   * returns `false`. Non-matches are left in place. */
+  bool Parse(std::vector<std::string>& flags) const;
+  bool Parse(std::vector<std::string>&& flags) const;
+
+  /* Write gflags `--helpxml` style output for a string-type flag. */
+  bool WriteGflagsCompatXml(std::ostream&) const;
+
+ private:
+  /* Reports whether `Process` wants to consume zero, one, or two arguments. */
+  enum class FlagProcessResult {
+    /* Error in handling a flag, exit flag handling with an error result. */
+    kFlagError,
+    kFlagSkip,                  /* Flag skipped; consume no arguments. */
+    kFlagConsumed,              /* Flag processed; consume one argument. */
+    kFlagConsumedWithFollowing, /* Flag processed; consume 2 arguments. */
+  };
+
+  void ValidateAlias(const FlagAlias& alias);
+  Flag& UnvalidatedAlias(const FlagAlias& alias) &;
+  Flag UnvalidatedAlias(const FlagAlias& alias) &&;
+
+  /* Attempt to match a single argument. */
+  FlagProcessResult Process(const std::string& argument,
+                            const std::optional<std::string>& next_arg) const;
+
+  bool HasAlias(const FlagAlias&) const;
+
+  friend std::ostream& operator<<(std::ostream&, const Flag&);
+  friend Flag InvalidFlagGuard();
+  friend Flag UnexpectedArgumentGuard();
+
+  std::vector<FlagAlias> aliases_;
+  std::optional<std::string> help_;
+  std::optional<std::function<std::string()>> getter_;
+  std::optional<std::function<bool(const FlagMatch&)>> setter_;
+};
+
+std::ostream& operator<<(std::ostream&, const Flag&);
+
+std::vector<std::string> ArgsToVec(int argc, char** argv);
+
+/* Handles a list of flags. Flags are matched in the order given in case two
+ * flags match the same argument. Matched flags are removed, leaving only
+ * unmatched arguments. */
+bool ParseFlags(const std::vector<Flag>& flags, std::vector<std::string>& args);
+bool ParseFlags(const std::vector<Flag>& flags, std::vector<std::string>&&);
+
+bool WriteGflagsCompatXml(const std::vector<Flag>&, std::ostream&);
+
+/* If any of these are used, they should be evaluated after all other flags, and
+ * in the order defined here (help before invalid flags, invalid flags before
+ * unexpected arguments). */
+
+/* If a "-help" or "--help" flag is present, prints all the flags and fails. */
+Flag HelpFlag(const std::vector<Flag>& flags, const std::string& text = "");
+/* Catches unrecognized arguments that begin with `-`, and errors out. This
+ * effectively denies unknown flags. */
+Flag InvalidFlagGuard();
+/* Catches any arguments not extracted by other Flag matchers and errors out.
+ * This effectively denies unknown flags and any positional arguments. */
+Flag UnexpectedArgumentGuard();
+
+// Create a flag resembling a gflags argument of the given type. This includes
+// "-[-]flag=*",support for all types, "-[-]noflag" support for booleans, and
+// "-flag *", "--flag *", support for other types. The value passed in the flag
+// is saved to the defined reference.
+Flag GflagsCompatFlag(const std::string& name);
+Flag GflagsCompatFlag(const std::string& name, std::string& value);
+Flag GflagsCompatFlag(const std::string& name, std::int32_t& value);
+Flag GflagsCompatFlag(const std::string& name, bool& value);
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/flag_parser_test.cpp b/common/libs/utils/flag_parser_test.cpp
new file mode 100644
index 0000000..ab16c84
--- /dev/null
+++ b/common/libs/utils/flag_parser_test.cpp
@@ -0,0 +1,267 @@
+/*
+ * 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.
+ */
+
+#include <common/libs/utils/flag_parser.h>
+
+#include <gtest/gtest.h>
+#include <libxml/tree.h>
+#include <map>
+#include <optional>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace cuttlefish {
+
+TEST(FlagParser, DuplicateAlias) {
+  FlagAlias alias = {FlagAliasMode::kFlagExact, "--flag"};
+  ASSERT_DEATH({ Flag().Alias(alias).Alias(alias); }, "Duplicate flag alias");
+}
+
+TEST(FlagParser, ConflictingAlias) {
+  FlagAlias exact_alias = {FlagAliasMode::kFlagExact, "--flag"};
+  FlagAlias following_alias = {FlagAliasMode::kFlagConsumesFollowing, "--flag"};
+  ASSERT_DEATH({ Flag().Alias(exact_alias).Alias(following_alias); },
+               "Overlapping flag aliases");
+}
+
+TEST(FlagParser, StringFlag) {
+  std::string value;
+  auto flag = GflagsCompatFlag("myflag", value);
+  ASSERT_TRUE(flag.Parse({"-myflag=a"}));
+  ASSERT_EQ(value, "a");
+  ASSERT_TRUE(flag.Parse({"--myflag=b"}));
+  ASSERT_EQ(value, "b");
+  ASSERT_TRUE(flag.Parse({"-myflag", "c"}));
+  ASSERT_EQ(value, "c");
+  ASSERT_TRUE(flag.Parse({"--myflag", "d"}));
+  ASSERT_EQ(value, "d");
+  ASSERT_TRUE(flag.Parse({"--myflag="}));
+  ASSERT_EQ(value, "");
+}
+
+std::optional<std::map<std::string, std::string>> flagXml(const Flag& f) {
+  std::stringstream xml_stream;
+  if (!f.WriteGflagsCompatXml(xml_stream)) {
+    return {};
+  }
+  auto xml = xml_stream.str();
+  // Holds all memory for the parsed structure.
+  std::unique_ptr<xmlDoc, xmlFreeFunc> doc(
+      xmlReadMemory(xml.c_str(), xml.size(), nullptr, nullptr, 0), xmlFree);
+  if (!doc) {
+    return {};
+  }
+  xmlNodePtr root_element = xmlDocGetRootElement(doc.get());
+  std::map<std::string, std::string> elements_map;
+  for (auto elem = root_element->children; elem != nullptr; elem = elem->next) {
+    if (elem->type != xmlElementType::XML_ELEMENT_NODE) {
+      continue;
+    }
+    elements_map[(char*)elem->name] = "";
+    if (elem->children == nullptr) {
+      continue;
+    }
+    if (elem->children->type != XML_TEXT_NODE) {
+      continue;
+    }
+    elements_map[(char*)elem->name] = (char*)elem->children->content;
+  }
+  return elements_map;
+}
+
+TEST(FlagParser, GflagsIncompatibleFlag) {
+  auto flag = Flag().Alias({FlagAliasMode::kFlagExact, "--flag"});
+  ASSERT_FALSE(flagXml(flag));
+}
+
+TEST(FlagParser, StringFlagXml) {
+  std::string value = "somedefault";
+  auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
+  auto xml = flagXml(flag);
+  ASSERT_TRUE(xml);
+  ASSERT_NE((*xml)["file"], "");
+  ASSERT_EQ((*xml)["name"], "myflag");
+  ASSERT_EQ((*xml)["meaning"], "somehelp");
+  ASSERT_EQ((*xml)["default"], "somedefault");
+  ASSERT_EQ((*xml)["current"], "somedefault");
+  ASSERT_EQ((*xml)["type"], "string");
+}
+
+TEST(FlagParser, RepeatedStringFlag) {
+  std::string value;
+  auto flag = GflagsCompatFlag("myflag", value);
+  ASSERT_TRUE(flag.Parse({"-myflag=a", "--myflag", "b"}));
+  ASSERT_EQ(value, "b");
+}
+
+TEST(FlagParser, RepeatedListFlag) {
+  std::vector<std::string> elems;
+  auto flag = GflagsCompatFlag("myflag");
+  flag.Setter([&elems](const FlagMatch& match) {
+    elems.push_back(match.value);
+    return true;
+  });
+  ASSERT_TRUE(flag.Parse({"-myflag=a", "--myflag", "b"}));
+  ASSERT_EQ(elems, (std::vector<std::string>{"a", "b"}));
+}
+
+TEST(FlagParser, FlagRemoval) {
+  std::string value;
+  auto flag = GflagsCompatFlag("myflag", value);
+  std::vector<std::string> flags = {"-myflag=a", "-otherflag=c"};
+  ASSERT_TRUE(flag.Parse(flags));
+  ASSERT_EQ(value, "a");
+  ASSERT_EQ(flags, std::vector<std::string>{"-otherflag=c"});
+  flags = {"-otherflag=a", "-myflag=c"};
+  ASSERT_TRUE(flag.Parse(flags));
+  ASSERT_EQ(value, "c");
+  ASSERT_EQ(flags, std::vector<std::string>{"-otherflag=a"});
+}
+
+TEST(FlagParser, IntFlag) {
+  std::int32_t value = 0;
+  auto flag = GflagsCompatFlag("myflag", value);
+  ASSERT_TRUE(flag.Parse({"-myflag=5"}));
+  ASSERT_EQ(value, 5);
+  ASSERT_TRUE(flag.Parse({"--myflag=6"}));
+  ASSERT_EQ(value, 6);
+  ASSERT_TRUE(flag.Parse({"-myflag", "7"}));
+  ASSERT_EQ(value, 7);
+  ASSERT_TRUE(flag.Parse({"--myflag", "8"}));
+  ASSERT_EQ(value, 8);
+}
+
+TEST(FlagParser, IntFlagXml) {
+  int value = 5;
+  auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
+  auto xml = flagXml(flag);
+  ASSERT_TRUE(xml);
+  ASSERT_NE((*xml)["file"], "");
+  ASSERT_EQ((*xml)["name"], "myflag");
+  ASSERT_EQ((*xml)["meaning"], "somehelp");
+  ASSERT_EQ((*xml)["default"], "5");
+  ASSERT_EQ((*xml)["current"], "5");
+  ASSERT_EQ((*xml)["type"], "string");
+}
+
+TEST(FlagParser, BoolFlag) {
+  bool value = false;
+  auto flag = GflagsCompatFlag("myflag", value);
+  ASSERT_TRUE(flag.Parse({"-myflag"}));
+  ASSERT_TRUE(value);
+
+  value = false;
+  ASSERT_TRUE(flag.Parse({"--myflag"}));
+  ASSERT_TRUE(value);
+
+  value = false;
+  ASSERT_TRUE(flag.Parse({"-myflag=true"}));
+  ASSERT_TRUE(value);
+
+  value = false;
+  ASSERT_TRUE(flag.Parse({"--myflag=true"}));
+  ASSERT_TRUE(value);
+
+  value = true;
+  ASSERT_TRUE(flag.Parse({"-nomyflag"}));
+  ASSERT_FALSE(value);
+
+  value = true;
+  ASSERT_TRUE(flag.Parse({"--nomyflag"}));
+  ASSERT_FALSE(value);
+
+  value = true;
+  ASSERT_TRUE(flag.Parse({"-myflag=false"}));
+  ASSERT_FALSE(value);
+
+  value = true;
+  ASSERT_TRUE(flag.Parse({"--myflag=false"}));
+  ASSERT_FALSE(value);
+
+  ASSERT_FALSE(flag.Parse({"--myflag=nonsense"}));
+}
+
+TEST(FlagParser, BoolFlagXml) {
+  bool value = true;
+  auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
+  auto xml = flagXml(flag);
+  ASSERT_TRUE(xml);
+  ASSERT_NE((*xml)["file"], "");
+  ASSERT_EQ((*xml)["name"], "myflag");
+  ASSERT_EQ((*xml)["meaning"], "somehelp");
+  ASSERT_EQ((*xml)["default"], "true");
+  ASSERT_EQ((*xml)["current"], "true");
+  ASSERT_EQ((*xml)["type"], "bool");
+}
+
+TEST(FlagParser, StringIntFlag) {
+  std::int32_t int_value = 0;
+  std::string string_value;
+  auto int_flag = GflagsCompatFlag("int", int_value);
+  auto string_flag = GflagsCompatFlag("string", string_value);
+  std::vector<Flag> flags = {int_flag, string_flag};
+  ASSERT_TRUE(ParseFlags(flags, {"-int=5", "-string=a"}));
+  ASSERT_EQ(int_value, 5);
+  ASSERT_EQ(string_value, "a");
+  ASSERT_TRUE(ParseFlags(flags, {"--int=6", "--string=b"}));
+  ASSERT_EQ(int_value, 6);
+  ASSERT_EQ(string_value, "b");
+  ASSERT_TRUE(ParseFlags(flags, {"-int", "7", "-string", "c"}));
+  ASSERT_EQ(int_value, 7);
+  ASSERT_EQ(string_value, "c");
+  ASSERT_TRUE(ParseFlags(flags, {"--int", "8", "--string", "d"}));
+  ASSERT_EQ(int_value, 8);
+  ASSERT_EQ(string_value, "d");
+}
+
+TEST(FlagParser, InvalidStringFlag) {
+  std::string value;
+  auto flag = GflagsCompatFlag("myflag", value);
+  ASSERT_FALSE(flag.Parse({"-myflag"}));
+  ASSERT_FALSE(flag.Parse({"--myflag"}));
+}
+
+TEST(FlagParser, InvalidIntFlag) {
+  int value;
+  auto flag = GflagsCompatFlag("myflag", value);
+  ASSERT_FALSE(flag.Parse({"-myflag"}));
+  ASSERT_FALSE(flag.Parse({"--myflag"}));
+  ASSERT_FALSE(flag.Parse({"-myflag=abc"}));
+  ASSERT_FALSE(flag.Parse({"--myflag=def"}));
+  ASSERT_FALSE(flag.Parse({"-myflag", "abc"}));
+  ASSERT_FALSE(flag.Parse({"--myflag", "def"}));
+}
+
+TEST(FlagParser, InvalidFlagGuard) {
+  auto flag = InvalidFlagGuard();
+  ASSERT_TRUE(flag.Parse({}));
+  ASSERT_TRUE(flag.Parse({"positional"}));
+  ASSERT_TRUE(flag.Parse({"positional", "positional2"}));
+  ASSERT_FALSE(flag.Parse({"-flag"}));
+  ASSERT_FALSE(flag.Parse({"-"}));
+}
+
+TEST(FlagParser, UnexpectedArgumentGuard) {
+  auto flag = UnexpectedArgumentGuard();
+  ASSERT_TRUE(flag.Parse({}));
+  ASSERT_FALSE(flag.Parse({"positional"}));
+  ASSERT_FALSE(flag.Parse({"positional", "positional2"}));
+  ASSERT_FALSE(flag.Parse({"-flag"}));
+  ASSERT_FALSE(flag.Parse({"-"}));
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/network.cpp b/common/libs/utils/network.cpp
index d1f5f59..ba69bed 100644
--- a/common/libs/utils/network.cpp
+++ b/common/libs/utils/network.cpp
@@ -36,12 +36,6 @@
 namespace cuttlefish {
 namespace {
 
-static std::string DefaultHostArtifactsPath(const std::string& file_name) {
-  return (StringFromEnv("ANDROID_HOST_OUT", StringFromEnv("HOME", ".")) +
-          "/") +
-         file_name;
-}
-
 // This should be the size of virtio_net_hdr_v1, from linux/virtio_net.h, but
 // the version of that header that ships with android in Pie does not include
 // that struct (it was added in Q).
@@ -96,42 +90,28 @@
     return tap_fd;
   }
 
-  if (HostArch() == Arch::Arm64) {
-    auto tapsetiff_path = DefaultHostArtifactsPath("bin/tapsetiff");
-    Command cmd(tapsetiff_path);
-    cmd.AddParameter(tap_fd);
-    cmd.AddParameter(interface_name.c_str());
-    int ret = cmd.Start().Wait();
-    if (ret != 0) {
-      LOG(ERROR) << "Unable to run tapsetiff.py. Exited with status " << ret;
-      tap_fd->Close();
-      return SharedFD();
-    }
-  } else {
-    struct ifreq ifr;
-    memset(&ifr, 0, sizeof(ifr));
-    ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR;
-    strncpy(ifr.ifr_name, interface_name.c_str(), IFNAMSIZ);
+  struct ifreq ifr;
+  memset(&ifr, 0, sizeof(ifr));
+  ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR;
+  strncpy(ifr.ifr_name, interface_name.c_str(), IFNAMSIZ);
 
-    int err = tap_fd->Ioctl(TUNSETIFF, &ifr);
-    if (err < 0) {
-      LOG(ERROR) << "Unable to connect to " << interface_name
-                 << " tap interface: " << tap_fd->StrError();
-      tap_fd->Close();
-      return SharedFD();
-    }
-
-    // The interface's configuration may have been modified or just not set
-    // correctly on creation. While qemu checks this and enforces the right
-    // configuration, crosvm does not, so it needs to be set before it's passed to
-    // it.
-    tap_fd->Ioctl(TUNSETOFFLOAD,
-                  reinterpret_cast<void*>(TUN_F_CSUM | TUN_F_UFO | TUN_F_TSO4 |
-                                        TUN_F_TSO6));
-    int len = SIZE_OF_VIRTIO_NET_HDR_V1;
-    tap_fd->Ioctl(TUNSETVNETHDRSZ, &len);
+  int err = tap_fd->Ioctl(TUNSETIFF, &ifr);
+  if (err < 0) {
+    LOG(ERROR) << "Unable to connect to " << interface_name
+               << " tap interface: " << tap_fd->StrError();
+    tap_fd->Close();
+    return SharedFD();
   }
 
+  // The interface's configuration may have been modified or just not set
+  // correctly on creation. While qemu checks this and enforces the right
+  // configuration, crosvm does not, so it needs to be set before it's passed to
+  // it.
+  tap_fd->Ioctl(TUNSETOFFLOAD,
+                reinterpret_cast<void*>(TUN_F_CSUM | TUN_F_UFO | TUN_F_TSO4 |
+                                        TUN_F_TSO6));
+  int len = SIZE_OF_VIRTIO_NET_HDR_V1;
+  tap_fd->Ioctl(TUNSETVNETHDRSZ, &len);
   return tap_fd;
 }
 
diff --git a/common/libs/utils/shared_fd_flag.cpp b/common/libs/utils/shared_fd_flag.cpp
new file mode 100644
index 0000000..9d5ad89
--- /dev/null
+++ b/common/libs/utils/shared_fd_flag.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+#include "common/libs/utils/shared_fd_flag.h"
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/flag_parser.h"
+
+namespace cuttlefish {
+
+static bool Set(const FlagMatch& match, SharedFD& out) {
+  int raw_fd;
+  if (!android::base::ParseInt(match.value.c_str(), &raw_fd)) {
+    LOG(ERROR) << "Failed to parse value \"" << match.value
+               << "\" for fd flag \"" << match.key << "\"";
+    return false;
+  }
+  out = SharedFD::Dup(raw_fd);
+  if (out->IsOpen()) {
+    close(raw_fd);
+  }
+  return true;
+}
+
+Flag SharedFDFlag(SharedFD& out) {
+  return Flag().Setter([&](const FlagMatch& mat) { return Set(mat, out); });
+}
+Flag SharedFDFlag(const std::string& name, SharedFD& out) {
+  return GflagsCompatFlag(name).Setter(
+      [&out](const FlagMatch& mat) { return Set(mat, out); });
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/common/libs/utils/shared_fd_flag.h
similarity index 69%
rename from common/libs/utils/size_utils.cpp
rename to common/libs/utils/shared_fd_flag.h
index 9f25445..33b1555 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/common/libs/utils/shared_fd_flag.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -13,16 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#pragma once
 
-#include "common/libs/utils/size_utils.h"
+#include <string>
 
-#include <unistd.h>
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/flag_parser.h"
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+Flag SharedFDFlag(SharedFD& out);
+Flag SharedFDFlag(const std::string& name, SharedFD& out);
 
 }  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.h b/common/libs/utils/size_utils.h
index 4158ddf..71bfea6 100644
--- a/common/libs/utils/size_utils.h
+++ b/common/libs/utils/size_utils.h
@@ -19,7 +19,16 @@
 
 namespace cuttlefish {
 
+// Keep the full disk size a multiple of 64k, for crosvm's virtio_blk driver
+constexpr int DISK_SIZE_SHIFT = 16;
+
+// Keep all partitions 4k aligned, for host performance reasons
+constexpr int PARTITION_SIZE_SHIFT = 12;
+
 // Returns the smallest multiple of 2^align_log greater than or equal to val.
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log);
+constexpr uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
+  uint64_t align = 1ULL << align_log;
+  return ((val + (align - 1)) / align) * align;
+}
 
 }  // namespace cuttlefish
diff --git a/common/libs/utils/subprocess.cpp b/common/libs/utils/subprocess.cpp
index 7c05219..b2613c1 100644
--- a/common/libs/utils/subprocess.cpp
+++ b/common/libs/utils/subprocess.cpp
@@ -143,7 +143,7 @@
   return retval;
 }
 
-bool KillSubprocess(Subprocess* subprocess) {
+StopperResult KillSubprocess(Subprocess* subprocess) {
   auto pid = subprocess->pid();
   if (pid > 0) {
     auto pgid = getpgid(pid);
@@ -155,13 +155,15 @@
       // to the process and not a (non-existent) group
     }
     bool is_group_head = pid == pgid;
-    if (is_group_head) {
-      return killpg(pid, SIGKILL) == 0;
-    } else {
-      return kill(pid, SIGKILL) == 0;
+    auto kill_ret = (is_group_head ? killpg : kill)(pid, SIGKILL);
+    if (kill_ret == 0) {
+      return StopperResult::kStopSuccess;
     }
+    auto kill_cmd = is_group_head ? "killpg(" : "kill(";
+    PLOG(ERROR) << kill_cmd << pid << ", SIGKILL) failed: ";
+    return StopperResult::kStopFailure;
   }
-  return true;
+  return StopperResult::kStopSuccess;
 }
 
 Command::~Command() {
@@ -175,41 +177,30 @@
   }
 }
 
-bool Command::BuildParameter(std::stringstream* stream, SharedFD shared_fd) {
+void Command::BuildParameter(std::stringstream* stream, SharedFD shared_fd) {
   int fd;
   if (inherited_fds_.count(shared_fd)) {
     fd = inherited_fds_[shared_fd];
   } else {
     fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
-    if (fd < 0) {
-      LOG(ERROR) << "Could not acquire a new file descriptor: " << shared_fd->StrError();
-      return false;
-    }
+    CHECK(fd >= 0) << "Could not acquire a new file descriptor: "
+                   << shared_fd->StrError();
     inherited_fds_[shared_fd] = fd;
   }
   *stream << fd;
-  return true;
 }
 
-bool Command::RedirectStdIO(Subprocess::StdIOChannel channel,
+void Command::RedirectStdIO(Subprocess::StdIOChannel channel,
                             SharedFD shared_fd) {
-  if (!shared_fd->IsOpen()) {
-    return false;
-  }
-  if (redirects_.count(channel)) {
-    LOG(ERROR) << "Attempted multiple redirections of fd: "
-               << static_cast<int>(channel);
-    return false;
-  }
+  CHECK(shared_fd->IsOpen());
+  CHECK(redirects_.count(channel) == 0)
+      << "Attempted multiple redirections of fd: " << static_cast<int>(channel);
   auto dup_fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
-  if (dup_fd < 0) {
-    LOG(ERROR) << "Could not acquire a new file descriptor: " << shared_fd->StrError();
-    return false;
-  }
+  CHECK(dup_fd >= 0) << "Could not acquire a new file descriptor: "
+                     << shared_fd->StrError();
   redirects_[channel] = dup_fd;
-  return true;
 }
-bool Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
+void Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
                             Subprocess::StdIOChannel parent_channel) {
   return RedirectStdIO(subprocess_channel,
                        SharedFD::Dup(static_cast<int>(parent_channel)));
@@ -315,11 +306,7 @@
                 << cmd.GetShortName() << "\"";
       return -1;
     }
-    if (!cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read)) {
-      LOG(ERROR) << "Could not set stdout of \"" << cmd.GetShortName()
-                << "\", was already set.";
-      return -1;
-    }
+    cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read);
     stdin_thread = std::thread([pipe_write, stdin, &io_error]() {
       int written = WriteAll(pipe_write, *stdin);
       if (written < 0) {
@@ -335,11 +322,7 @@
                 << cmd.GetShortName() << "\"";
       return -1;
     }
-    if (!cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write)) {
-      LOG(ERROR) << "Could not set stdout of \"" << cmd.GetShortName()
-                << "\", was already set.";
-      return -1;
-    }
+    cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write);
     stdout_thread = std::thread([pipe_read, stdout, &io_error]() {
       int read = ReadAll(pipe_read, stdout);
       if (read < 0) {
@@ -355,11 +338,7 @@
                 << cmd.GetShortName() << "\"";
       return -1;
     }
-    if (!cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write)) {
-      LOG(ERROR) << "Could not set stderr of \"" << cmd.GetShortName()
-                << "\", was already set.";
-      return -1;
-    }
+    cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write);
     stderr_thread = std::thread([pipe_read, stderr, &io_error]() {
       int read = ReadAll(pipe_read, stderr);
       if (read < 0) {
diff --git a/common/libs/utils/subprocess.h b/common/libs/utils/subprocess.h
index 777a026..50c86b9 100644
--- a/common/libs/utils/subprocess.h
+++ b/common/libs/utils/subprocess.h
@@ -29,12 +29,19 @@
 #include <common/libs/fs/shared_fd.h>
 
 namespace cuttlefish {
+
+enum class StopperResult {
+  kStopFailure, /* Failed to stop the subprocess. */
+  kStopCrash,   /* Attempted to stop the subprocess cleanly, but that failed. */
+  kStopSuccess, /* The subprocess exited in the expected way. */
+};
+
 class Command;
 class Subprocess;
 class SubprocessOptions;
-using SubprocessStopper = std::function<bool(Subprocess*)>;
+using SubprocessStopper = std::function<StopperResult(Subprocess*)>;
 // Kills a process by sending it the SIGKILL signal.
-bool KillSubprocess(Subprocess* subprocess);
+StopperResult KillSubprocess(Subprocess* subprocess);
 
 // Keeps track of a running (sub)process. Allows to wait for its completion.
 // It's an error to wait twice for the same subprocess.
@@ -65,7 +72,7 @@
   // completion of the command, that's what Wait is for.
   bool Started() const { return started_; }
   pid_t pid() const { return pid_; }
-  bool Stop() { return stopper_(this); }
+  StopperResult Stop() { return stopper_(this); }
 
  private:
   // Copy is disabled to avoid waiting twice for the same pid (the first wait
@@ -108,15 +115,15 @@
  private:
   template <typename T>
   // For every type other than SharedFD (for which there is a specialisation)
-  bool BuildParameter(std::stringstream* stream, T t) {
+  void BuildParameter(std::stringstream* stream, T t) {
     *stream << t;
-    return true;
   }
   // Special treatment for SharedFD
-  bool BuildParameter(std::stringstream* stream, SharedFD shared_fd);
+  void BuildParameter(std::stringstream* stream, SharedFD shared_fd);
   template <typename T, typename... Args>
-  bool BuildParameter(std::stringstream* stream, T t, Args... args) {
-    return BuildParameter(stream, t) && BuildParameter(stream, args...);
+  void BuildParameter(std::stringstream* stream, T t, Args... args) {
+    BuildParameter(stream, t);
+    BuildParameter(stream, args...);
   }
 
  public:
@@ -136,6 +143,14 @@
   Command& operator=(const Command&) = delete;
   ~Command();
 
+  const std::string& Executable() const { return command_[0]; }
+
+  void SetExecutable(const std::string& executable) {
+    command_[0] = executable;
+  }
+
+  void SetStopper(SubprocessStopper stopper) { subprocess_stopper_ = stopper; }
+
   // Specify the environment for the subprocesses to be started. By default
   // subprocesses inherit the parent's environment.
   void SetEnvironment(const std::vector<std::string>& env) {
@@ -156,33 +171,24 @@
   // object is destroyed. To add multiple parameters to the command the function
   // must be called multiple times, one per parameter.
   template <typename... Args>
-  bool AddParameter(Args... args) {
+  void AddParameter(Args... args) {
     std::stringstream ss;
-    if (BuildParameter(&ss, args...)) {
-      command_.push_back(ss.str());
-      return true;
-    }
-    return false;
+    BuildParameter(&ss, args...);
+    command_.push_back(ss.str());
   }
   // Similar to AddParameter, except the args are appended to the last (most
   // recently-added) parameter in the command.
   template <typename... Args>
-  bool AppendToLastParameter(Args... args) {
-    if (command_.empty()) {
-      LOG(ERROR) << "There is no parameter to append to.";
-      return false;
-    }
+  void AppendToLastParameter(Args... args) {
+    CHECK(!command_.empty()) << "There is no parameter to append to.";
     std::stringstream ss;
-    if (BuildParameter(&ss, args...)) {
-      command_[command_.size()-1] += ss.str();
-      return true;
-    }
-    return false;
+    BuildParameter(&ss, args...);
+    command_[command_.size() - 1] += ss.str();
   }
 
   // Redirects the standard IO of the command.
-  bool RedirectStdIO(Subprocess::StdIOChannel channel, SharedFD shared_fd);
-  bool RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
+  void RedirectStdIO(Subprocess::StdIOChannel channel, SharedFD shared_fd);
+  void RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
                      Subprocess::StdIOChannel parent_channel);
 
   // Starts execution of the command. This method can be called multiple times,
diff --git a/common/libs/utils/vsock_connection.cpp b/common/libs/utils/vsock_connection.cpp
new file mode 100644
index 0000000..6142b69
--- /dev/null
+++ b/common/libs/utils/vsock_connection.cpp
@@ -0,0 +1,210 @@
+/*
+ * 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.
+ */
+
+#include "common/libs/utils/vsock_connection.h"
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_select.h"
+
+#include "android-base/logging.h"
+
+namespace cuttlefish {
+
+VsockConnection::~VsockConnection() { Disconnect(); }
+
+std::future<bool> VsockConnection::ConnectAsync(unsigned int port,
+                                                unsigned int cid) {
+  return std::async(std::launch::async,
+                    [this, port, cid]() { return Connect(port, cid); });
+}
+
+void VsockConnection::Disconnect() {
+  LOG(INFO) << "Disconnecting with fd status:" << fd_->StrError();
+  fd_->Shutdown(SHUT_RDWR);
+  if (disconnect_callback_) {
+    disconnect_callback_();
+  }
+  fd_->Close();
+}
+
+void VsockConnection::SetDisconnectCallback(std::function<void()> callback) {
+  disconnect_callback_ = callback;
+}
+
+bool VsockConnection::IsConnected() const { return fd_->IsOpen(); }
+
+bool VsockConnection::DataAvailable() const {
+  SharedFDSet read_set;
+  read_set.Set(fd_);
+  struct timeval timeout = {0, 0};
+  return Select(&read_set, nullptr, nullptr, &timeout) > 0;
+}
+
+int32_t VsockConnection::Read() {
+  std::lock_guard<std::recursive_mutex> lock(read_mutex_);
+  int32_t result;
+  if (ReadExactBinary(fd_, &result) != sizeof(result)) {
+    Disconnect();
+    return 0;
+  }
+  return result;
+}
+
+bool VsockConnection::Read(std::vector<char>& data) {
+  std::lock_guard<std::recursive_mutex> lock(read_mutex_);
+  return ReadExact(fd_, &data) == data.size();
+}
+
+std::vector<char> VsockConnection::Read(size_t size) {
+  if (size == 0) {
+    return {};
+  }
+  std::lock_guard<std::recursive_mutex> lock(read_mutex_);
+  std::vector<char> result(size);
+  if (ReadExact(fd_, &result) != size) {
+    Disconnect();
+    return {};
+  }
+  return result;
+}
+
+std::future<std::vector<char>> VsockConnection::ReadAsync(size_t size) {
+  return std::async(std::launch::async, [this, size]() { return Read(size); });
+}
+
+// Message format is buffer size followed by buffer data
+std::vector<char> VsockConnection::ReadMessage() {
+  std::lock_guard<std::recursive_mutex> lock(read_mutex_);
+  auto size = Read();
+  if (size < 0) {
+    Disconnect();
+    return {};
+  }
+  return Read(size);
+}
+
+bool VsockConnection::ReadMessage(std::vector<char>& data) {
+  std::lock_guard<std::recursive_mutex> lock(read_mutex_);
+  auto size = Read();
+  if (size < 0) {
+    Disconnect();
+    return false;
+  }
+  data.resize(size);
+  return Read(data);
+}
+
+std::future<std::vector<char>> VsockConnection::ReadMessageAsync() {
+  return std::async(std::launch::async, [this]() { return ReadMessage(); });
+}
+
+Json::Value VsockConnection::ReadJsonMessage() {
+  auto msg = ReadMessage();
+  Json::CharReaderBuilder builder;
+  Json::CharReader* reader = builder.newCharReader();
+  Json::Value json_msg;
+  std::string errors;
+  if (!reader->parse(msg.data(), msg.data() + msg.size(), &json_msg, &errors)) {
+    return {};
+  }
+  return json_msg;
+}
+
+std::future<Json::Value> VsockConnection::ReadJsonMessageAsync() {
+  return std::async(std::launch::async, [this]() { return ReadJsonMessage(); });
+}
+
+bool VsockConnection::Write(int32_t data) {
+  std::lock_guard<std::recursive_mutex> lock(write_mutex_);
+  if (WriteAllBinary(fd_, &data) != sizeof(data)) {
+    Disconnect();
+    return false;
+  }
+  return true;
+}
+
+bool VsockConnection::Write(const char* data, unsigned int size) {
+  std::lock_guard<std::recursive_mutex> lock(write_mutex_);
+  if (WriteAll(fd_, data, size) != size) {
+    Disconnect();
+    return false;
+  }
+  return true;
+}
+
+bool VsockConnection::Write(const std::vector<char>& data) {
+  return Write(data.data(), data.size());
+}
+
+// Message format is buffer size followed by buffer data
+bool VsockConnection::WriteMessage(const std::string& data) {
+  return Write(data.size()) && Write(data.c_str(), data.length());
+}
+
+bool VsockConnection::WriteMessage(const std::vector<char>& data) {
+  std::lock_guard<std::recursive_mutex> lock(write_mutex_);
+  return Write(data.size()) && Write(data);
+}
+
+bool VsockConnection::WriteMessage(const Json::Value& data) {
+  Json::StreamWriterBuilder factory;
+  std::string message_str = Json::writeString(factory, data);
+  return WriteMessage(message_str);
+}
+
+bool VsockConnection::WriteStrides(const char* data, unsigned int size,
+                                   unsigned int num_strides, int stride_size) {
+  const char* src = data;
+  for (unsigned int i = 0; i < num_strides; ++i, src += stride_size) {
+    if (!Write(src, size)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool VsockClientConnection::Connect(unsigned int port, unsigned int cid) {
+  fd_ = SharedFD::VsockClient(cid, port, SOCK_STREAM);
+  if (!fd_->IsOpen()) {
+    LOG(ERROR) << "Failed to connect:" << fd_->StrError();
+  }
+  return fd_->IsOpen();
+}
+
+VsockServerConnection::~VsockServerConnection() { ServerShutdown(); }
+
+void VsockServerConnection::ServerShutdown() {
+  if (server_fd_->IsOpen()) {
+    LOG(INFO) << __FUNCTION__
+              << ": server fd status:" << server_fd_->StrError();
+    server_fd_->Shutdown(SHUT_RDWR);
+    server_fd_->Close();
+  }
+}
+
+bool VsockServerConnection::Connect(unsigned int port, unsigned int cid) {
+  if (!server_fd_->IsOpen()) {
+    server_fd_ = cuttlefish::SharedFD::VsockServer(port, SOCK_STREAM, cid);
+  }
+  if (server_fd_->IsOpen()) {
+    fd_ = SharedFD::Accept(*server_fd_);
+    return fd_->IsOpen();
+  } else {
+    return false;
+  }
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/vsock_connection.h b/common/libs/utils/vsock_connection.h
new file mode 100644
index 0000000..1a16b2f
--- /dev/null
+++ b/common/libs/utils/vsock_connection.h
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <json/json.h>
+#include <functional>
+#include <future>
+#include <mutex>
+#include <vector>
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+
+class VsockConnection {
+ public:
+  virtual ~VsockConnection();
+  virtual bool Connect(unsigned int port, unsigned int cid) = 0;
+  virtual void Disconnect();
+  std::future<bool> ConnectAsync(unsigned int port, unsigned int cid);
+  void SetDisconnectCallback(std::function<void()> callback);
+
+  bool IsConnected() const;
+  bool DataAvailable() const;
+
+  int32_t Read();
+  bool Read(std::vector<char>& data);
+  std::vector<char> Read(size_t size);
+  std::future<std::vector<char>> ReadAsync(size_t size);
+
+  bool ReadMessage(std::vector<char>& data);
+  std::vector<char> ReadMessage();
+  std::future<std::vector<char>> ReadMessageAsync();
+  Json::Value ReadJsonMessage();
+  std::future<Json::Value> ReadJsonMessageAsync();
+
+  bool Write(int32_t data);
+  bool Write(const char* data, unsigned int size);
+  bool Write(const std::vector<char>& data);
+  bool WriteMessage(const std::string& data);
+  bool WriteMessage(const std::vector<char>& data);
+  bool WriteMessage(const Json::Value& data);
+  bool WriteStrides(const char* data, unsigned int size,
+                    unsigned int num_strides, int stride_size);
+
+ protected:
+  std::recursive_mutex read_mutex_;
+  std::recursive_mutex write_mutex_;
+  std::function<void()> disconnect_callback_;
+  SharedFD fd_;
+};
+
+class VsockClientConnection : public VsockConnection {
+ public:
+  bool Connect(unsigned int port, unsigned int cid) override;
+};
+
+class VsockServerConnection : public VsockConnection {
+ public:
+  virtual ~VsockServerConnection();
+  void ServerShutdown();
+  bool Connect(unsigned int port, unsigned int cid) override;
+
+ private:
+  SharedFD server_fd_;
+};
+
+}  // namespace cuttlefish
diff --git a/default-permissions.xml b/default-permissions.xml
index bd2ed2c..038ffce 100644
--- a/default-permissions.xml
+++ b/default-permissions.xml
@@ -67,27 +67,6 @@
         <permission name="android.permission.RECEIVE_SMS" fixed="false"/>
     </exception>
 
-    <exception
-            package="com.google.android.projection.gearhead"
-            sha256-cert-digest="FD:B0:0C:43:DB:DE:8B:51:CB:31:2A:A8:1D:3B:5F:A1:77:13:AD:B9:4B:28:F5:98:D7:7F:8E:B8:9D:AC:EE:DF">
-        <!-- Gearhead legacy -->
-        <permission name="android.permission.ACCESS_FINE_LOCATION" fixed="false"/>
-        <permission name="android.permission.CALL_PHONE" fixed="false"/>
-        <permission name="android.permission.READ_CALL_LOG" fixed="false"/>
-        <permission name="android.permission.READ_CONTACTS" fixed="false"/>
-        <permission name="android.permission.READ_PHONE_STATE" fixed="false"/>
-        <permission name="android.permission.RECEIVE_SMS" fixed="false"/>
-        <permission name="android.permission.RECORD_AUDIO" fixed="false"/>
-        <permission name="android.permission.SEND_SMS" fixed="false"/>
-        <permission name="android.permission.READ_CALENDAR" fixed="false"/>
-        <!-- For Top Gear -->
-        <permission name="android.permission.PROCESS_OUTGOING_CALLS" fixed="false"/>
-        <permission name="android.permission.READ_SMS" fixed="false"/>
-        <permission name="android.permission.RECEIVE_MMS" fixed="false"/>
-        <permission name="android.permission.WRITE_CALL_LOG" fixed="false"/>
-        <permission name="android.permission.ACCESS_COARSE_LOCATION" fixed="false"/>
-    </exception>
-
     <exception package="com.google.android.settings.intelligence">
         <!-- Calendar -->
         <permission name="android.permission.READ_CALENDAR" fixed="true"/>
diff --git a/guest/Android.mk b/guest/Android.mk
new file mode 100644
index 0000000..428f4b5
--- /dev/null
+++ b/guest/Android.mk
@@ -0,0 +1,18 @@
+# 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/commands/bt_vhci_forwarder/Android.bp b/guest/commands/bt_vhci_forwarder/Android.bp
index 72dd7c9..75e006e 100644
--- a/guest/commands/bt_vhci_forwarder/Android.bp
+++ b/guest/commands/bt_vhci_forwarder/Android.bp
@@ -6,6 +6,7 @@
     name: "bt_vhci_forwarder",
     srcs: [
         "main.cpp",
+        ":H4PacketizerSources",
     ],
     shared_libs: [
         "libbase",
@@ -14,7 +15,12 @@
     ],
     static_libs: [
         "libgflags",
-        "libbt-rootcanal",
+    ],
+    include_dirs: [
+        "system/bt/vendor_libs/test_vendor_lib/include",
+        "system/bt/vendor_libs/test_vendor_lib",
+        "system/bt",
+        "system/bt/gd",
     ],
     defaults: ["cuttlefish_guest_only"]
 }
diff --git a/host/commands/tapsetiff/Android.bp b/guest/commands/dlkm_loader/Android.bp
similarity index 68%
rename from host/commands/tapsetiff/Android.bp
rename to guest/commands/dlkm_loader/Android.bp
index 1d7dedb..ae91c02 100644
--- a/host/commands/tapsetiff/Android.bp
+++ b/guest/commands/dlkm_loader/Android.bp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2020 The Android Open Source Project
+// 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.
@@ -17,7 +17,17 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-sh_binary_host {
-    name: "tapsetiff",
-    src: "tapsetiff.py",
+cc_binary {
+    name: "dlkm_loader",
+    srcs: [
+        "dlkm_loader.cpp",
+    ],
+    static_libs: [
+        "libbase",
+        "libmodprobe",
+    ],
+    shared_libs: [
+        "liblog",
+    ],
+    defaults: ["cuttlefish_guest_only"]
 }
diff --git a/guest/commands/dlkm_loader/dlkm_loader.cpp b/guest/commands/dlkm_loader/dlkm_loader.cpp
new file mode 100644
index 0000000..0d22225
--- /dev/null
+++ b/guest/commands/dlkm_loader/dlkm_loader.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <modprobe/modprobe.h>
+
+int main(void) {
+  LOG(INFO) << "dlkm loader successfully initialized";
+  Modprobe m({"/vendor/lib/modules"}, "modules.load");
+  CHECK(m.LoadListedModules(true)) << "modules from vendor dlkm weren't loaded correctly";
+  LOG(INFO) << "module load count is " << m.GetModuleCount();
+  return 0;
+}
diff --git a/guest/commands/rotate/main.cpp b/guest/commands/rotate/main.cpp
deleted file mode 100644
index 8967097..0000000
--- a/guest/commands/rotate/main.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <android-base/chrono_utils.h>
-#include <android-base/logging.h>
-#include <binder/IServiceManager.h>
-#include <utils/StrongPointer.h>
-#include <utils/SystemClock.h>
-
-#include <thread>
-
-#include "android/hardware/sensors/2.0/ISensors.h"
-
-using android::sp;
-using android::hardware::sensors::V1_0::Event;
-using android::hardware::sensors::V1_0::OperationMode;
-using android::hardware::sensors::V1_0::Result;
-using android::hardware::sensors::V1_0::SensorInfo;
-using android::hardware::sensors::V1_0::SensorStatus;
-using android::hardware::sensors::V1_0::SensorType;
-using android::hardware::sensors::V2_0::ISensors;
-
-void InjectOrientation(bool portrait) {
-  const sp<ISensors> sensors = ISensors::getService();
-  if (sensors == nullptr) {
-    LOG(FATAL) << "Unable to get ISensors.";
-  }
-
-  Result result;
-
-  // Place the ISensors HAL into DATA_INJECTION mode so that we can
-  // inject events.
-  result = sensors->setOperationMode(OperationMode::DATA_INJECTION);
-  if (result != Result::OK) {
-    LOG(FATAL) << "Unable to set ISensors operation mode to DATA_INJECTION: "
-               << toString(result);
-  }
-
-  // Find the first available accelerometer sensor.
-  int accel_handle = -1;
-  const auto& getSensorsList_result =
-      sensors->getSensorsList([&](const auto& list) {
-        for (const SensorInfo& sensor : list) {
-          if (sensor.type == SensorType::ACCELEROMETER) {
-            accel_handle = sensor.sensorHandle;
-            break;
-          }
-        }
-      });
-  if (!getSensorsList_result.isOk()) {
-    LOG(FATAL) << "Unable to get ISensors sensors list: "
-               << getSensorsList_result.description();
-  }
-  if (accel_handle == -1) {
-    LOG(FATAL) << "Unable to find ACCELEROMETER sensor.";
-  }
-
-  // Create a base ISensors accelerometer event.
-  Event event;
-  event.sensorHandle = accel_handle;
-  event.sensorType = SensorType::ACCELEROMETER;
-  if (portrait) {
-    event.u.vec3.x = 0;
-    event.u.vec3.y = 9.2;
-  } else {
-    event.u.vec3.x = 9.2;
-    event.u.vec3.y = 0;
-  }
-  event.u.vec3.z = 3.5;
-  event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
-
-  // Repeatedly inject accelerometer events. The WindowManager orientation
-  // listener responds to sustained accelerometer data, not just a single event.
-  android::base::Timer timer;
-  while (timer.duration() < 1s) {
-    event.timestamp = android::elapsedRealtimeNano();
-    result = sensors->injectSensorData(event);
-    if (result != Result::OK) {
-      LOG(FATAL) << "Unable to inject ISensors accelerometer event: "
-                 << toString(result);
-    }
-    std::this_thread::sleep_for(10ms);
-  }
-
-  // Return the ISensors HAL back to NORMAL mode.
-  result = sensors->setOperationMode(OperationMode::NORMAL);
-  if (result != Result::OK) {
-    LOG(FATAL) << "Unable to set sensors operation mode to NORMAL: "
-               << toString(result);
-  }
-}
-
-int main(int argc, char** argv) {
-  if (argc == 1) {
-    LOG(FATAL) << "Expected command line arg 'portrait' or 'landscape'";
-  }
-
-  bool portrait = true;
-  if (!strcmp(argv[1], "portrait")) {
-    portrait = true;
-  } else if (!strcmp(argv[1], "landscape")) {
-    portrait = false;
-  } else {
-    LOG(FATAL) << "Expected command line arg 'portrait' or 'landscape'";
-  }
-
-  InjectOrientation(portrait);
-}
diff --git a/guest/commands/rotate/Android.bp b/guest/commands/sensor_injection/Android.bp
similarity index 79%
rename from guest/commands/rotate/Android.bp
rename to guest/commands/sensor_injection/Android.bp
index 6568c0e..a29250a 100644
--- a/guest/commands/rotate/Android.bp
+++ b/guest/commands/sensor_injection/Android.bp
@@ -3,11 +3,11 @@
 }
 
 cc_binary {
-    name: "cuttlefish_rotate",
+    name: "cuttlefish_sensor_injection",
     srcs: ["main.cpp"],
     shared_libs: [
         "android.hardware.sensors@1.0",
-        "android.hardware.sensors@2.0",
+        "android.hardware.sensors@2.1",
         "libbase",
         "libbinder",
         "libhidlbase",
diff --git a/guest/commands/sensor_injection/main.cpp b/guest/commands/sensor_injection/main.cpp
new file mode 100644
index 0000000..6eadb4a
--- /dev/null
+++ b/guest/commands/sensor_injection/main.cpp
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+#include <android-base/chrono_utils.h>
+#include <android-base/logging.h>
+#include <binder/IServiceManager.h>
+#include <utils/StrongPointer.h>
+#include <utils/SystemClock.h>
+
+#include <thread>
+
+#include "android/hardware/sensors/2.1/ISensors.h"
+
+using android::sp;
+using android::hardware::sensors::V1_0::OperationMode;
+using android::hardware::sensors::V1_0::Result;
+using android::hardware::sensors::V1_0::SensorStatus;
+using android::hardware::sensors::V2_1::Event;
+using android::hardware::sensors::V2_1::ISensors;
+using android::hardware::sensors::V2_1::SensorInfo;
+using android::hardware::sensors::V2_1::SensorType;
+
+sp<ISensors> startSensorInjection() {
+  const sp<ISensors> sensors = ISensors::getService();
+  if (sensors == nullptr) {
+    LOG(FATAL) << "Unable to get ISensors.";
+  }
+
+  // Place the ISensors HAL into DATA_INJECTION mode so that we can
+  // inject events.
+  Result result = sensors->setOperationMode(OperationMode::DATA_INJECTION);
+  if (result != Result::OK) {
+    LOG(FATAL) << "Unable to set ISensors operation mode to DATA_INJECTION: "
+               << toString(result);
+  }
+
+  return sensors;
+}
+
+int getSensorHandle(SensorType type, const sp<ISensors> sensors) {
+  // Find the first available sensor of the given type.
+  int handle = -1;
+  const auto& getSensorsList_result =
+      sensors->getSensorsList_2_1([&](const auto& list) {
+        for (const SensorInfo& sensor : list) {
+          if (sensor.type == type) {
+            handle = sensor.sensorHandle;
+            break;
+          }
+        }
+      });
+  if (!getSensorsList_result.isOk()) {
+    LOG(FATAL) << "Unable to get ISensors sensors list: "
+               << getSensorsList_result.description();
+  }
+  if (handle == -1) {
+    LOG(FATAL) << "Unable to find sensor.";
+  }
+  return handle;
+}
+
+void endSensorInjection(const sp<ISensors> sensors) {
+  // Return the ISensors HAL back to NORMAL mode.
+  Result result = sensors->setOperationMode(OperationMode::NORMAL);
+  if (result != Result::OK) {
+    LOG(FATAL) << "Unable to set sensors operation mode to NORMAL: "
+               << toString(result);
+  }
+}
+
+// Inject ACCELEROMETER events to corresponding to a given physical
+// device orientation: portrait or landscape.
+void InjectOrientation(bool portrait) {
+  sp<ISensors> sensors = startSensorInjection();
+  int handle = getSensorHandle(SensorType::ACCELEROMETER, sensors);
+
+  // Create a base ISensors accelerometer event.
+  Event event;
+  event.sensorHandle = handle;
+  event.sensorType = SensorType::ACCELEROMETER;
+  if (portrait) {
+    event.u.vec3.x = 0;
+    event.u.vec3.y = 9.2;
+  } else {
+    event.u.vec3.x = 9.2;
+    event.u.vec3.y = 0;
+  }
+  event.u.vec3.z = 3.5;
+  event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
+
+  // Repeatedly inject accelerometer events. The WindowManager orientation
+  // listener responds to sustained accelerometer data, not just a single event.
+  android::base::Timer timer;
+  Result result;
+  while (timer.duration() < 1s) {
+    event.timestamp = android::elapsedRealtimeNano();
+    result = sensors->injectSensorData_2_1(event);
+    if (result != Result::OK) {
+      LOG(FATAL) << "Unable to inject ISensors accelerometer event: "
+                 << toString(result);
+    }
+    std::this_thread::sleep_for(10ms);
+  }
+
+  endSensorInjection(sensors);
+}
+
+// Inject a single HINGE_ANGLE event at the given angle.
+void InjectHingeAngle(int angle) {
+  sp<ISensors> sensors = startSensorInjection();
+  int handle = getSensorHandle(SensorType::HINGE_ANGLE, sensors);
+
+  // Create a base ISensors hinge_angle event.
+  Event event;
+  event.sensorHandle = handle;
+  event.sensorType = SensorType::HINGE_ANGLE;
+  event.u.scalar = angle;
+  event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
+  event.timestamp = android::elapsedRealtimeNano();
+  Result result = sensors->injectSensorData_2_1(event);
+  if (result != Result::OK) {
+    LOG(FATAL) << "Unable to inject HINGE_ANGLE data: " << toString(result);
+  }
+
+  endSensorInjection(sensors);
+}
+
+int main(int argc, char** argv) {
+  if (argc == 2) {
+    LOG(FATAL) << "Expected command line args 'rotate <portrait|landscape>' or "
+                  "'hinge_angle <value>'";
+  }
+
+  if (!strcmp(argv[1], "rotate")) {
+    bool portrait = true;
+    if (!strcmp(argv[2], "portrait")) {
+      portrait = true;
+    } else if (!strcmp(argv[2], "landscape")) {
+      portrait = false;
+    } else {
+      LOG(FATAL) << "Expected command line arg 'portrait' or 'landscape'";
+    }
+    InjectOrientation(portrait);
+  } else if (!strcmp(argv[1], "hinge_angle")) {
+    int angle = std::stoi(argv[2]);
+    if (angle < 0 || angle > 360) {
+      LOG(FATAL) << "Bad hinge_angle value: " << argv[2];
+    }
+    InjectHingeAngle(angle);
+  } else {
+    LOG(FATAL) << "Unknown arg: " << argv[1];
+  }
+}
diff --git a/guest/commands/setup_wifi/main.cpp b/guest/commands/setup_wifi/main.cpp
index 0a07c97..310db79 100644
--- a/guest/commands/setup_wifi/main.cpp
+++ b/guest/commands/setup_wifi/main.cpp
@@ -32,17 +32,15 @@
 #include "common/libs/net/network_interface.h"
 #include "common/libs/net/network_interface_manager.h"
 
-DEFINE_string(mac_address, "", "mac address to use for wlan0");
+DEFINE_string(mac_prefix, "", "mac prefix to use for wlan0");
 
-static std::array<unsigned char, 6> str_to_mac(const std::string& mac_str) {
+static std::array<unsigned char, 6> prefix_to_mac(
+    const std::string& mac_prefix) {
   std::array<unsigned char, 6> mac;
-  std::istringstream stream(mac_str);
-  for (int i = 0; i < 6; i++) {
-    int num;
-    stream >> std::hex >> num;
-    mac[i] = num;
-    stream.get();
-  }
+  int macPrefix = stoi(mac_prefix);
+  mac[0] = 0x02;
+  mac[1] = (macPrefix >> CHAR_BIT) & 0xFF;
+  mac[2] = macPrefix & 0xFF;
   return mac;
 }
 
@@ -51,7 +49,8 @@
   auto factory = cuttlefish::NetlinkClientFactory::Default();
   std::unique_ptr<cuttlefish::NetlinkClient> nl(factory->New(NETLINK_ROUTE));
 
-  LOG(INFO) << "Setting " << source << " mac address to " << FLAGS_mac_address;
+  LOG(INFO) << "Setting " << source << " mac address based on "
+            << FLAGS_mac_prefix;
   int32_t index = if_nametoindex(source.c_str());
   // Setting the address is available in RTM_SETLINK, but not RTM_NEWLINK.
   // https://elixir.bootlin.com/linux/v5.4.44/source/net/core/rtnetlink.c#L2785
@@ -64,7 +63,7 @@
     .ifi_index = index,
     .ifi_change = 0xFFFFFFFF,
   });
-  fix_mac_request.AddMacAddress(str_to_mac(FLAGS_mac_address));
+  fix_mac_request.AddMacAddress(prefix_to_mac(FLAGS_mac_prefix));
   bool fix_mac = nl->Send(fix_mac_request);
   if (!fix_mac) {
     LOG(ERROR) << "setup_network: could not fix mac address";
@@ -131,17 +130,17 @@
 }
 
 int main(int argc, char** argv) {
-  char wifi_address[PROPERTY_VALUE_MAX + 1];
-  property_get("ro.boot.wifi_mac_address", wifi_address, "");
+  char wifi_mac_prefix[PROPERTY_VALUE_MAX + 1];
+  property_get("ro.boot.wifi_mac_prefix", wifi_mac_prefix, "");
 
-  SetCommandLineOptionWithMode("mac_address", wifi_address,
+  SetCommandLineOptionWithMode("mac_prefix", wifi_mac_prefix,
                                google::FlagSettingMode::SET_FLAGS_DEFAULT);
 
   gflags::ParseCommandLineFlags(&argc, &argv, true);
 
-  int renamed_eth0 = RenameNetwork("eth0", "buried_eth0");
-  if (renamed_eth0 != 0) {
-    return renamed_eth0;
+  int renamed_eth1 = RenameNetwork("eth1", "buried_eth0");
+  if (renamed_eth1 != 0) {
+    return renamed_eth1;
   }
   return CreateWifiWrapper("buried_eth0", "wlan0");
 }
diff --git a/guest/hals/audio/Android.bp b/guest/hals/audio/Android.bp
deleted file mode 100644
index c6faae7..0000000
--- a/guest/hals/audio/Android.bp
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_library_shared {
-    name: "audio.primary.cutf",
-    relative_install_path: "hw",
-    defaults: ["cuttlefish_guest_only"],
-    vendor: true,
-    srcs: ["audio_hw.c"],
-    cflags: ["-Wno-unused-parameter"],
-    shared_libs: ["libcutils", "libhardware", "liblog", "libtinyalsa"],
-}
diff --git a/guest/hals/audio/audio_hw.c b/guest/hals/audio/audio_hw.c
deleted file mode 100644
index 652d747..0000000
--- a/guest/hals/audio/audio_hw.c
+++ /dev/null
@@ -1,1848 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- *
- * This code was forked from device/generic/goldfish/audio/audio_hw.c
- *
- * At the time of forking, the code was identical except that a fallback
- * to a legacy HAL which does not use ALSA was removed, and the dependency
- * on libdl was also removed.
- */
-
-#define LOG_TAG "audio_hw_generic"
-
-#include <assert.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <pthread.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <sys/time.h>
-#include <dlfcn.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-#include <log/log.h>
-#include <cutils/list.h>
-#include <cutils/str_parms.h>
-
-#include <hardware/hardware.h>
-#include <system/audio.h>
-#include <hardware/audio.h>
-#include <tinyalsa/asoundlib.h>
-
-#define PCM_CARD 0
-#define PCM_DEVICE 0
-
-#define OUT_PERIOD_MS 10
-#define OUT_PERIOD_COUNT 4
-
-#define IN_PERIOD_MS 10
-#define IN_PERIOD_COUNT 4
-
-struct generic_audio_device {
-    struct audio_hw_device device;          // Constant after init
-    pthread_mutex_t lock;
-    bool mic_mute;                          // Protected by this->lock
-    struct mixer* mixer;                    // Protected by this->lock
-    struct listnode out_streams;            // Record for output streams, protected by this->lock
-    struct listnode in_streams;             // Record for input streams, protected by this->lock
-    audio_patch_handle_t next_patch_handle; // Protected by this->lock
-};
-
-/* If not NULL, this is a pointer to the fallback module.
- * This really is the original goldfish audio device /dev/eac which we will use
- * if no alsa devices are detected.
- */
-static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state);
-static int adev_get_microphones(const audio_hw_device_t *dev,
-                                struct audio_microphone_characteristic_t *mic_array,
-                                size_t *mic_count);
-
-
-typedef struct audio_vbuffer {
-    pthread_mutex_t lock;
-    uint8_t *  data;
-    size_t     frame_size;
-    size_t     frame_count;
-    size_t     head;
-    size_t     tail;
-    size_t     live;
-} audio_vbuffer_t;
-
-static int audio_vbuffer_init (audio_vbuffer_t * audio_vbuffer, size_t frame_count,
-                              size_t frame_size) {
-    if (!audio_vbuffer) {
-        return -EINVAL;
-    }
-    audio_vbuffer->frame_size = frame_size;
-    audio_vbuffer->frame_count = frame_count;
-    size_t bytes = frame_count * frame_size;
-    audio_vbuffer->data = calloc(bytes, 1);
-    if (!audio_vbuffer->data) {
-        return -ENOMEM;
-    }
-    audio_vbuffer->head = 0;
-    audio_vbuffer->tail = 0;
-    audio_vbuffer->live = 0;
-    pthread_mutex_init (&audio_vbuffer->lock, (const pthread_mutexattr_t *) NULL);
-    return 0;
-}
-
-static int audio_vbuffer_destroy (audio_vbuffer_t * audio_vbuffer) {
-    if (!audio_vbuffer) {
-        return -EINVAL;
-    }
-    free(audio_vbuffer->data);
-    pthread_mutex_destroy(&audio_vbuffer->lock);
-    return 0;
-}
-
-static int audio_vbuffer_live (audio_vbuffer_t * audio_vbuffer) {
-    if (!audio_vbuffer) {
-        return -EINVAL;
-    }
-    pthread_mutex_lock (&audio_vbuffer->lock);
-    int live = audio_vbuffer->live;
-    pthread_mutex_unlock (&audio_vbuffer->lock);
-    return live;
-}
-
-#define MIN(a,b) (((a)<(b))?(a):(b))
-static size_t audio_vbuffer_write (audio_vbuffer_t * audio_vbuffer, const void * buffer, size_t frame_count) {
-    size_t frames_written = 0;
-    pthread_mutex_lock (&audio_vbuffer->lock);
-
-    while (frame_count != 0) {
-        int frames = 0;
-        if (audio_vbuffer->live == 0 || audio_vbuffer->head > audio_vbuffer->tail) {
-            frames = MIN(frame_count, audio_vbuffer->frame_count - audio_vbuffer->head);
-        } else if (audio_vbuffer->head < audio_vbuffer->tail) {
-            frames = MIN(frame_count, audio_vbuffer->tail - (audio_vbuffer->head));
-        } else {
-            // Full
-            break;
-        }
-        memcpy(&audio_vbuffer->data[audio_vbuffer->head*audio_vbuffer->frame_size],
-               &((uint8_t*)buffer)[frames_written*audio_vbuffer->frame_size],
-               frames*audio_vbuffer->frame_size);
-        audio_vbuffer->live += frames;
-        frames_written += frames;
-        frame_count -= frames;
-        audio_vbuffer->head = (audio_vbuffer->head + frames) % audio_vbuffer->frame_count;
-    }
-
-    pthread_mutex_unlock (&audio_vbuffer->lock);
-    return frames_written;
-}
-
-static size_t audio_vbuffer_read (audio_vbuffer_t * audio_vbuffer, void * buffer, size_t frame_count) {
-    size_t frames_read = 0;
-    pthread_mutex_lock (&audio_vbuffer->lock);
-
-    while (frame_count != 0) {
-        int frames = 0;
-        if (audio_vbuffer->live == audio_vbuffer->frame_count ||
-            audio_vbuffer->tail > audio_vbuffer->head) {
-            frames = MIN(frame_count, audio_vbuffer->frame_count - audio_vbuffer->tail);
-        } else if (audio_vbuffer->tail < audio_vbuffer->head) {
-            frames = MIN(frame_count, audio_vbuffer->head - audio_vbuffer->tail);
-        } else {
-            break;
-        }
-        memcpy(&((uint8_t*)buffer)[frames_read*audio_vbuffer->frame_size],
-               &audio_vbuffer->data[audio_vbuffer->tail*audio_vbuffer->frame_size],
-               frames*audio_vbuffer->frame_size);
-        audio_vbuffer->live -= frames;
-        frames_read += frames;
-        frame_count -= frames;
-        audio_vbuffer->tail = (audio_vbuffer->tail + frames) % audio_vbuffer->frame_count;
-    }
-
-    pthread_mutex_unlock (&audio_vbuffer->lock);
-    return frames_read;
-}
-
-struct generic_stream_out {
-    struct audio_stream_out stream;                 // Constant after init
-    pthread_mutex_t lock;
-    struct generic_audio_device *dev;               // Constant after init
-    uint32_t num_devices;                           // Protected by this->lock
-    audio_devices_t devices[AUDIO_PATCH_PORTS_MAX]; // Protected by this->lock
-    struct audio_config req_config;                 // Constant after init
-    struct pcm_config pcm_config;                   // Constant after init
-    audio_vbuffer_t buffer;                         // Constant after init
-
-    // Time & Position Keeping
-    bool standby;                      // Protected by this->lock
-    uint64_t underrun_position;        // Protected by this->lock
-    struct timespec underrun_time;     // Protected by this->lock
-    uint64_t last_write_time_us;       // Protected by this->lock
-    uint64_t frames_total_buffered;    // Protected by this->lock
-    uint64_t frames_written;           // Protected by this->lock
-    uint64_t frames_rendered;          // Protected by this->lock
-
-    // Worker
-    pthread_t worker_thread;          // Constant after init
-    pthread_cond_t worker_wake;       // Protected by this->lock
-    bool worker_standby;              // Protected by this->lock
-    bool worker_exit;                 // Protected by this->lock
-
-    audio_io_handle_t handle;          // Constant after init
-    audio_patch_handle_t patch_handle; // Protected by this->dev->lock
-
-    struct listnode stream_node;       // Protected by this->dev->lock
-};
-
-struct generic_stream_in {
-    struct audio_stream_in stream;    // Constant after init
-    pthread_mutex_t lock;
-    struct generic_audio_device *dev; // Constant after init
-    audio_devices_t device;           // Protected by this->lock
-    struct audio_config req_config;   // Constant after init
-    struct pcm *pcm;                  // Protected by this->lock
-    struct pcm_config pcm_config;     // Constant after init
-    int16_t *stereo_to_mono_buf;      // Protected by this->lock
-    size_t stereo_to_mono_buf_size;   // Protected by this->lock
-    audio_vbuffer_t buffer;           // Protected by this->lock
-
-    // Time & Position Keeping
-    bool standby;                     // Protected by this->lock
-    int64_t standby_position;         // Protected by this->lock
-    struct timespec standby_exit_time;// Protected by this->lock
-    int64_t standby_frames_read;      // Protected by this->lock
-
-    // Worker
-    pthread_t worker_thread;          // Constant after init
-    pthread_cond_t worker_wake;       // Protected by this->lock
-    bool worker_standby;              // Protected by this->lock
-    bool worker_exit;                 // Protected by this->lock
-
-    audio_io_handle_t handle;          // Constant after init
-    audio_patch_handle_t patch_handle; // Protected by this->dev->lock
-
-    struct listnode stream_node;       // Protected by this->dev->lock
-};
-
-static struct pcm_config pcm_config_out = {
-    .channels = 2,
-    .rate = 0,
-    .period_size = 0,
-    .period_count = OUT_PERIOD_COUNT,
-    .format = PCM_FORMAT_S16_LE,
-    .start_threshold = 0,
-};
-
-static struct pcm_config pcm_config_in = {
-    .channels = 2,
-    .rate = 0,
-    .period_size = 0,
-    .period_count = IN_PERIOD_COUNT,
-    .format = PCM_FORMAT_S16_LE,
-    .start_threshold = 0,
-    .stop_threshold = INT_MAX,
-};
-
-static pthread_mutex_t adev_init_lock = PTHREAD_MUTEX_INITIALIZER;
-static unsigned int audio_device_ref_count = 0;
-
-static uint32_t out_get_sample_rate(const struct audio_stream *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    return out->req_config.sample_rate;
-}
-
-static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate)
-{
-    return -ENOSYS;
-}
-
-static size_t out_get_buffer_size(const struct audio_stream *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    int size = out->pcm_config.period_size *
-                audio_stream_out_frame_size(&out->stream);
-
-    return size;
-}
-
-static audio_channel_mask_t out_get_channels(const struct audio_stream *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    return out->req_config.channel_mask;
-}
-
-static audio_format_t out_get_format(const struct audio_stream *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-
-    return out->req_config.format;
-}
-
-static int out_set_format(struct audio_stream *stream, audio_format_t format)
-{
-    return -ENOSYS;
-}
-
-static int out_dump(const struct audio_stream *stream, int fd)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    pthread_mutex_lock(&out->lock);
-    dprintf(fd, "\tout_dump:\n"
-                "\t\tsample rate: %u\n"
-                "\t\tbuffer size: %zu\n"
-                "\t\tchannel mask: %08x\n"
-                "\t\tformat: %d\n"
-                "\t\tdevice(s): ",
-                out_get_sample_rate(stream),
-                out_get_buffer_size(stream),
-                out_get_channels(stream),
-                out_get_format(stream));
-    if (out->num_devices == 0) {
-        dprintf(fd, "%08x\n", AUDIO_DEVICE_NONE);
-    } else {
-        for (uint32_t i = 0; i < out->num_devices; i++) {
-            if (i != 0) {
-                dprintf(fd, ", ");
-            }
-            dprintf(fd, "%08x", out->devices[i]);
-        }
-        dprintf(fd, "\n");
-    }
-    dprintf(fd, "\t\taudio dev: %p\n\n", out->dev);
-    pthread_mutex_unlock(&out->lock);
-    return 0;
-}
-
-static int out_set_parameters(struct audio_stream *stream, const char *kvpairs)
-{
-    struct str_parms *parms;
-    char value[32];
-    int success;
-    int ret = -EINVAL;
-
-    if (kvpairs == NULL || kvpairs[0] == 0) {
-        return 0;
-    }
-    parms = str_parms_create_str(kvpairs);
-    success = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING,
-            value, sizeof(value));
-    // As the hal version is 3.0, it must not use set parameters API to set audio devices.
-    // Instead, it should use create_audio_patch API.
-    assert(("Must not use set parameters API to set audio devices", success < 0));
-
-    if (str_parms_has_key(parms, AUDIO_PARAMETER_STREAM_FORMAT)) {
-        // match the return value of out_set_format
-        ret = -ENOSYS;
-    }
-
-    str_parms_destroy(parms);
-
-    if (ret == -EINVAL) {
-        ALOGW("%s(), unsupported parameter %s", __func__, kvpairs);
-        // There is not any key supported for set_parameters API.
-        // Return error when there is non-null value passed in.
-    }
-    return ret;
-}
-
-static char * out_get_parameters(const struct audio_stream *stream, const char *keys)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    struct str_parms *query = str_parms_create_str(keys);
-    char *str = NULL;
-    char value[256];
-    struct str_parms *reply = str_parms_create();
-    int ret;
-    bool get = false;
-
-    ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value));
-    if (ret >= 0) {
-        pthread_mutex_lock(&out->lock);
-        audio_devices_t device = AUDIO_DEVICE_NONE;
-        for (uint32_t i = 0; i < out->num_devices; i++) {
-            device |= out->devices[i];
-        }
-        str_parms_add_int(reply, AUDIO_PARAMETER_STREAM_ROUTING, device);
-        pthread_mutex_unlock(&out->lock);
-        get = true;
-    }
-
-    if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_SUP_FORMATS)) {
-        value[0] = 0;
-        strcat(value, "AUDIO_FORMAT_PCM_16_BIT");
-        str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_SUP_FORMATS, value);
-        get = true;
-    }
-
-    if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_FORMAT)) {
-        value[0] = 0;
-        strcat(value, "AUDIO_FORMAT_PCM_16_BIT");
-        str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_FORMAT, value);
-        get = true;
-    }
-
-    if (get) {
-        str = str_parms_to_str(reply);
-    }
-    else {
-        ALOGD("%s Unsupported paramter: %s", __FUNCTION__, keys);
-    }
-
-    str_parms_destroy(query);
-    str_parms_destroy(reply);
-    return str;
-}
-
-static uint32_t out_get_latency(const struct audio_stream_out *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    return (out->pcm_config.period_size * 1000) / out->pcm_config.rate;
-}
-
-static int out_set_volume(struct audio_stream_out *stream, float left,
-                          float right)
-{
-    return -ENOSYS;
-}
-
-static void *out_write_worker(void * args)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)args;
-    struct pcm *pcm = NULL;
-    uint8_t *buffer = NULL;
-    int buffer_frames;
-    int buffer_size;
-    bool restart = false;
-    bool shutdown = false;
-    while (true) {
-        pthread_mutex_lock(&out->lock);
-        while (out->worker_standby || restart) {
-            restart = false;
-            if (pcm) {
-                pcm_close(pcm); // Frees pcm
-                pcm = NULL;
-                free(buffer);
-                buffer=NULL;
-            }
-            if (out->worker_exit) {
-                break;
-            }
-            pthread_cond_wait(&out->worker_wake, &out->lock);
-        }
-
-        if (out->worker_exit) {
-            if (!out->worker_standby) {
-                ALOGE("Out worker not in standby before exiting");
-            }
-            shutdown = true;
-        }
-
-        while (!shutdown && audio_vbuffer_live(&out->buffer) == 0) {
-            pthread_cond_wait(&out->worker_wake, &out->lock);
-        }
-
-        if (shutdown) {
-            pthread_mutex_unlock(&out->lock);
-            break;
-        }
-
-        if (!pcm) {
-            pcm = pcm_open(PCM_CARD, PCM_DEVICE,
-                          PCM_OUT | PCM_MONOTONIC, &out->pcm_config);
-            if (!pcm_is_ready(pcm)) {
-                ALOGE("pcm_open(out) failed: %s: channels %d format %d rate %d",
-                  pcm_get_error(pcm),
-                  out->pcm_config.channels,
-                  out->pcm_config.format,
-                  out->pcm_config.rate
-                   );
-                pthread_mutex_unlock(&out->lock);
-                break;
-            }
-            buffer_frames = out->pcm_config.period_size;
-            buffer_size = pcm_frames_to_bytes(pcm, buffer_frames);
-            buffer = malloc(buffer_size);
-            if (!buffer) {
-                ALOGE("could not allocate write buffer");
-                pthread_mutex_unlock(&out->lock);
-                break;
-            }
-        }
-        int frames = audio_vbuffer_read(&out->buffer, buffer, buffer_frames);
-        pthread_mutex_unlock(&out->lock);
-        int ret = pcm_write(pcm, buffer, pcm_frames_to_bytes(pcm, frames));
-        if (ret != 0) {
-            ALOGE("pcm_write failed %s", pcm_get_error(pcm));
-            restart = true;
-        }
-    }
-    if (buffer) {
-        free(buffer);
-    }
-
-    return NULL;
-}
-
-// Call with in->lock held
-static void get_current_output_position(struct generic_stream_out *out,
-                                       uint64_t * position,
-                                       struct timespec * timestamp) {
-    struct timespec curtime = { .tv_sec = 0, .tv_nsec = 0 };
-    clock_gettime(CLOCK_MONOTONIC, &curtime);
-    const int64_t now_us = (curtime.tv_sec * 1000000000LL + curtime.tv_nsec) / 1000;
-    if (timestamp) {
-        *timestamp = curtime;
-    }
-    int64_t position_since_underrun;
-    if (out->standby) {
-        position_since_underrun = 0;
-    } else {
-        const int64_t first_us = (out->underrun_time.tv_sec * 1000000000LL +
-                                  out->underrun_time.tv_nsec) / 1000;
-        position_since_underrun = (now_us - first_us) *
-                out_get_sample_rate(&out->stream.common) /
-                1000000;
-        if (position_since_underrun < 0) {
-            position_since_underrun = 0;
-        }
-    }
-    *position = out->underrun_position + position_since_underrun;
-
-    // The device will reuse the same output stream leading to periods of
-    // underrun.
-    if (*position > out->frames_written) {
-        ALOGW("Not supplying enough data to HAL, expected position %" PRIu64 " , only wrote "
-              "%" PRIu64,
-              *position, out->frames_written);
-
-        *position = out->frames_written;
-        out->underrun_position = *position;
-        out->underrun_time = curtime;
-        out->frames_total_buffered = 0;
-    }
-}
-
-
-static ssize_t out_write(struct audio_stream_out *stream, const void *buffer,
-                         size_t bytes)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    const size_t frames =  bytes / audio_stream_out_frame_size(stream);
-
-    pthread_mutex_lock(&out->lock);
-
-    if (out->worker_standby) {
-        out->worker_standby = false;
-    }
-
-    uint64_t current_position;
-    struct timespec current_time;
-
-    get_current_output_position(out, &current_position, &current_time);
-    const uint64_t now_us = (current_time.tv_sec * 1000000000LL +
-                             current_time.tv_nsec) / 1000;
-    if (out->standby) {
-        out->standby = false;
-        out->underrun_time = current_time;
-        out->frames_rendered = 0;
-        out->frames_total_buffered = 0;
-    }
-
-    size_t frames_written = audio_vbuffer_write(&out->buffer, buffer, frames);
-    pthread_cond_signal(&out->worker_wake);
-
-    /* Implementation just consumes bytes if we start getting backed up */
-    out->frames_written += frames;
-    out->frames_rendered += frames;
-    out->frames_total_buffered += frames;
-
-    // We simulate the audio device blocking when it's write buffers become
-    // full.
-
-    // At the beginning or after an underrun, try to fill up the vbuffer.
-    // This will be throttled by the PlaybackThread
-    int frames_sleep = out->frames_total_buffered < out->buffer.frame_count ? 0 : frames;
-
-    uint64_t sleep_time_us = frames_sleep * 1000000LL /
-                            out_get_sample_rate(&stream->common);
-
-    // If the write calls are delayed, subtract time off of the sleep to
-    // compensate
-    uint64_t time_since_last_write_us = now_us - out->last_write_time_us;
-    if (time_since_last_write_us < sleep_time_us) {
-        sleep_time_us -= time_since_last_write_us;
-    } else {
-        sleep_time_us = 0;
-    }
-    out->last_write_time_us = now_us + sleep_time_us;
-
-    pthread_mutex_unlock(&out->lock);
-
-    if (sleep_time_us > 0) {
-        usleep(sleep_time_us);
-    }
-
-    if (frames_written < frames) {
-        ALOGW("Hardware backing HAL too slow, could only write %zu of %zu frames", frames_written, frames);
-    }
-
-    /* Always consume all bytes */
-    return bytes;
-}
-
-static int out_get_presentation_position(const struct audio_stream_out *stream,
-                                   uint64_t *frames, struct timespec *timestamp)
-
-{
-    if (stream == NULL || frames == NULL || timestamp == NULL) {
-        return -EINVAL;
-    }
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-
-    pthread_mutex_lock(&out->lock);
-    get_current_output_position(out, frames, timestamp);
-    pthread_mutex_unlock(&out->lock);
-
-    return 0;
-}
-
-static int out_get_render_position(const struct audio_stream_out *stream,
-                                   uint32_t *dsp_frames)
-{
-    if (stream == NULL || dsp_frames == NULL) {
-        return -EINVAL;
-    }
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    pthread_mutex_lock(&out->lock);
-    *dsp_frames = out->frames_rendered;
-    pthread_mutex_unlock(&out->lock);
-    return 0;
-}
-
-// Must be called with out->lock held
-static void do_out_standby(struct generic_stream_out *out)
-{
-    int frames_sleep = 0;
-    uint64_t sleep_time_us = 0;
-    if (out->standby) {
-        return;
-    }
-    while (true) {
-        get_current_output_position(out, &out->underrun_position, NULL);
-        frames_sleep = out->frames_written - out->underrun_position;
-
-        if (frames_sleep == 0) {
-            break;
-        }
-
-        sleep_time_us = frames_sleep * 1000000LL /
-                        out_get_sample_rate(&out->stream.common);
-
-        pthread_mutex_unlock(&out->lock);
-        usleep(sleep_time_us);
-        pthread_mutex_lock(&out->lock);
-    }
-    out->worker_standby = true;
-    out->standby = true;
-}
-
-static int out_standby(struct audio_stream *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    pthread_mutex_lock(&out->lock);
-    do_out_standby(out);
-    pthread_mutex_unlock(&out->lock);
-    return 0;
-}
-
-static int out_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
-{
-    // out_add_audio_effect is a no op
-    return 0;
-}
-
-static int out_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
-{
-    // out_remove_audio_effect is a no op
-    return 0;
-}
-
-static int out_get_next_write_timestamp(const struct audio_stream_out *stream,
-                                        int64_t *timestamp)
-{
-    return -ENOSYS;
-}
-
-static uint32_t in_get_sample_rate(const struct audio_stream *stream)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    return in->req_config.sample_rate;
-}
-
-static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate)
-{
-    return -ENOSYS;
-}
-
-static int refine_output_parameters(uint32_t *sample_rate, audio_format_t *format, audio_channel_mask_t *channel_mask)
-{
-    static const uint32_t sample_rates [] = {8000,11025,16000,22050,24000,32000,
-                                            44100,48000};
-    static const int sample_rates_count = sizeof(sample_rates)/sizeof(uint32_t);
-    bool inval = false;
-    if (*format != AUDIO_FORMAT_PCM_16_BIT) {
-        *format = AUDIO_FORMAT_PCM_16_BIT;
-        inval = true;
-    }
-
-    int channel_count = popcount(*channel_mask);
-    if (channel_count != 1 && channel_count != 2) {
-        *channel_mask = AUDIO_CHANNEL_IN_STEREO;
-        inval = true;
-    }
-
-    int i;
-    for (i = 0; i < sample_rates_count; i++) {
-        if (*sample_rate < sample_rates[i]) {
-            *sample_rate = sample_rates[i];
-            inval=true;
-            break;
-        }
-        else if (*sample_rate == sample_rates[i]) {
-            break;
-        }
-        else if (i == sample_rates_count-1) {
-            // Cap it to the highest rate we support
-            *sample_rate = sample_rates[i];
-            inval=true;
-        }
-    }
-
-    if (inval) {
-        return -EINVAL;
-    }
-    return 0;
-}
-
-static int refine_input_parameters(uint32_t *sample_rate, audio_format_t *format, audio_channel_mask_t *channel_mask)
-{
-    static const uint32_t sample_rates [] = {8000, 11025, 16000, 22050, 44100, 48000};
-    static const int sample_rates_count = sizeof(sample_rates)/sizeof(uint32_t);
-    bool inval = false;
-    // Only PCM_16_bit is supported. If this is changed, stereo to mono drop
-    // must be fixed in in_read
-    if (*format != AUDIO_FORMAT_PCM_16_BIT) {
-        *format = AUDIO_FORMAT_PCM_16_BIT;
-        inval = true;
-    }
-
-    int channel_count = popcount(*channel_mask);
-    if (channel_count != 1 && channel_count != 2) {
-        *channel_mask = AUDIO_CHANNEL_IN_STEREO;
-        inval = true;
-    }
-
-    int i;
-    for (i = 0; i < sample_rates_count; i++) {
-        if (*sample_rate < sample_rates[i]) {
-            *sample_rate = sample_rates[i];
-            inval=true;
-            break;
-        }
-        else if (*sample_rate == sample_rates[i]) {
-            break;
-        }
-        else if (i == sample_rates_count-1) {
-            // Cap it to the highest rate we support
-            *sample_rate = sample_rates[i];
-            inval=true;
-        }
-    }
-
-    if (inval) {
-        return -EINVAL;
-    }
-    return 0;
-}
-
-static int check_input_parameters(uint32_t sample_rate, audio_format_t format,
-                                  audio_channel_mask_t channel_mask)
-{
-    return refine_input_parameters(&sample_rate, &format, &channel_mask);
-}
-
-static size_t get_input_buffer_size(uint32_t sample_rate, audio_format_t format,
-                                    audio_channel_mask_t channel_mask)
-{
-    size_t size;
-    int channel_count = popcount(channel_mask);
-    if (check_input_parameters(sample_rate, format, channel_mask) != 0)
-        return 0;
-
-    size = sample_rate*IN_PERIOD_MS/1000;
-    // Audioflinger expects audio buffers to be multiple of 16 frames
-    size = ((size + 15) / 16) * 16;
-    size *= sizeof(short) * channel_count;
-
-    return size;
-}
-
-
-static size_t in_get_buffer_size(const struct audio_stream *stream)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    int size = get_input_buffer_size(in->req_config.sample_rate,
-                                 in->req_config.format,
-                                 in->req_config.channel_mask);
-
-    return size;
-}
-
-static audio_channel_mask_t in_get_channels(const struct audio_stream *stream)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    return in->req_config.channel_mask;
-}
-
-static audio_format_t in_get_format(const struct audio_stream *stream)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    return in->req_config.format;
-}
-
-static int in_set_format(struct audio_stream *stream, audio_format_t format)
-{
-    return -ENOSYS;
-}
-
-static int in_dump(const struct audio_stream *stream, int fd)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-
-    pthread_mutex_lock(&in->lock);
-    dprintf(fd, "\tin_dump:\n"
-                "\t\tsample rate: %u\n"
-                "\t\tbuffer size: %zu\n"
-                "\t\tchannel mask: %08x\n"
-                "\t\tformat: %d\n"
-                "\t\tdevice: %08x\n"
-                "\t\taudio dev: %p\n\n",
-                in_get_sample_rate(stream),
-                in_get_buffer_size(stream),
-                in_get_channels(stream),
-                in_get_format(stream),
-                in->device,
-                in->dev);
-    pthread_mutex_unlock(&in->lock);
-    return 0;
-}
-
-static int in_set_parameters(struct audio_stream *stream, const char *kvpairs)
-{
-    struct str_parms *parms;
-    char value[32];
-    int success;
-    int ret = -EINVAL;
-
-    if (kvpairs == NULL || kvpairs[0] == 0) {
-        return 0;
-    }
-    parms = str_parms_create_str(kvpairs);
-    success = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING,
-            value, sizeof(value));
-    // As the hal version is 3.0, it must not use set parameters API to set audio device.
-    // Instead, it should use create_audio_patch API.
-    assert(("Must not use set parameters API to set audio devices", success < 0));
-
-    if (str_parms_has_key(parms, AUDIO_PARAMETER_STREAM_FORMAT)) {
-        // match the return value of in_set_format
-        ret = -ENOSYS;
-    }
-
-    str_parms_destroy(parms);
-
-    if (ret == -EINVAL) {
-        ALOGW("%s(), unsupported parameter %s", __func__, kvpairs);
-        // There is not any key supported for set_parameters API.
-        // Return error when there is non-null value passed in.
-    }
-    return ret;
-}
-
-static char * in_get_parameters(const struct audio_stream *stream,
-                                const char *keys)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    struct str_parms *query = str_parms_create_str(keys);
-    char *str = NULL;
-    char value[256];
-    struct str_parms *reply = str_parms_create();
-    int ret;
-    bool get = false;
-
-    ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value));
-    if (ret >= 0) {
-        str_parms_add_int(reply, AUDIO_PARAMETER_STREAM_ROUTING, in->device);
-        get = true;
-    }
-
-    if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_SUP_FORMATS)) {
-        value[0] = 0;
-        strcat(value, "AUDIO_FORMAT_PCM_16_BIT");
-        str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_SUP_FORMATS, value);
-        get = true;
-    }
-
-    if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_FORMAT)) {
-        value[0] = 0;
-        strcat(value, "AUDIO_FORMAT_PCM_16_BIT");
-        str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_FORMAT, value);
-        get = true;
-    }
-
-    if (get) {
-        str = str_parms_to_str(reply);
-    }
-    else {
-        ALOGD("%s Unsupported paramter: %s", __FUNCTION__, keys);
-    }
-
-    str_parms_destroy(query);
-    str_parms_destroy(reply);
-    return str;
-}
-
-static int in_set_gain(struct audio_stream_in *stream, float gain)
-{
-    // in_set_gain is a no op
-    return 0;
-}
-
-// Call with in->lock held
-static void get_current_input_position(struct generic_stream_in *in,
-                                       int64_t * position,
-                                       struct timespec * timestamp) {
-    struct timespec t = { .tv_sec = 0, .tv_nsec = 0 };
-    clock_gettime(CLOCK_MONOTONIC, &t);
-    const int64_t now_us = (t.tv_sec * 1000000000LL + t.tv_nsec) / 1000;
-    if (timestamp) {
-        *timestamp = t;
-    }
-    int64_t position_since_standby;
-    if (in->standby) {
-        position_since_standby = 0;
-    } else {
-        const int64_t first_us = (in->standby_exit_time.tv_sec * 1000000000LL +
-                                  in->standby_exit_time.tv_nsec) / 1000;
-        position_since_standby = (now_us - first_us) *
-                in_get_sample_rate(&in->stream.common) /
-                1000000;
-        if (position_since_standby < 0) {
-            position_since_standby = 0;
-        }
-    }
-    *position = in->standby_position + position_since_standby;
-}
-
-// Must be called with in->lock held
-static void do_in_standby(struct generic_stream_in *in)
-{
-    if (in->standby) {
-        return;
-    }
-    in->worker_standby = true;
-    get_current_input_position(in, &in->standby_position, NULL);
-    in->standby = true;
-}
-
-static int in_standby(struct audio_stream *stream)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    pthread_mutex_lock(&in->lock);
-    do_in_standby(in);
-    pthread_mutex_unlock(&in->lock);
-    return 0;
-}
-
-static void *in_read_worker(void * args)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)args;
-    struct pcm *pcm = NULL;
-    uint8_t *buffer = NULL;
-    size_t buffer_frames;
-    int buffer_size;
-
-    bool restart = false;
-    bool shutdown = false;
-    while (true) {
-        pthread_mutex_lock(&in->lock);
-        while (in->worker_standby || restart) {
-            restart = false;
-            if (pcm) {
-                pcm_close(pcm); // Frees pcm
-                pcm = NULL;
-                free(buffer);
-                buffer=NULL;
-            }
-            if (in->worker_exit) {
-                break;
-            }
-            pthread_cond_wait(&in->worker_wake, &in->lock);
-        }
-
-        if (in->worker_exit) {
-            if (!in->worker_standby) {
-                ALOGE("In worker not in standby before exiting");
-            }
-            shutdown = true;
-        }
-        if (shutdown) {
-            pthread_mutex_unlock(&in->lock);
-            break;
-        }
-        if (!pcm) {
-            pcm = pcm_open(PCM_CARD, PCM_DEVICE,
-                          PCM_IN | PCM_MONOTONIC, &in->pcm_config);
-            if (!pcm_is_ready(pcm)) {
-                ALOGE("pcm_open(in) failed: %s: channels %d format %d rate %d",
-                  pcm_get_error(pcm),
-                  in->pcm_config.channels,
-                  in->pcm_config.format,
-                  in->pcm_config.rate
-                   );
-                pthread_mutex_unlock(&in->lock);
-                break;
-            }
-            buffer_frames = in->pcm_config.period_size;
-            buffer_size = pcm_frames_to_bytes(pcm, buffer_frames);
-            buffer = malloc(buffer_size);
-            if (!buffer) {
-                ALOGE("could not allocate worker read buffer");
-                pthread_mutex_unlock(&in->lock);
-                break;
-            }
-        }
-        pthread_mutex_unlock(&in->lock);
-        int ret = pcm_read(pcm, buffer, pcm_frames_to_bytes(pcm, buffer_frames));
-        if (ret != 0) {
-            ALOGW("pcm_read failed %s", pcm_get_error(pcm));
-            restart = true;
-            continue;
-        }
-
-        pthread_mutex_lock(&in->lock);
-        size_t frames_written = audio_vbuffer_write(&in->buffer, buffer, buffer_frames);
-        pthread_mutex_unlock(&in->lock);
-
-        if (frames_written != buffer_frames) {
-            ALOGW("in_read_worker only could write %zu / %zu frames", frames_written, buffer_frames);
-        }
-    }
-    if (buffer) {
-        free(buffer);
-    }
-    return NULL;
-}
-
-static ssize_t in_read(struct audio_stream_in *stream, void* buffer,
-                       size_t bytes)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    struct generic_audio_device *adev = in->dev;
-    const size_t frames =  bytes / audio_stream_in_frame_size(stream);
-    bool mic_mute = false;
-    size_t read_bytes = 0;
-
-    adev_get_mic_mute(&adev->device, &mic_mute);
-    pthread_mutex_lock(&in->lock);
-
-    if (in->worker_standby) {
-        in->worker_standby = false;
-    }
-    pthread_cond_signal(&in->worker_wake);
-
-    int64_t current_position;
-    struct timespec current_time;
-
-    get_current_input_position(in, &current_position, &current_time);
-    if (in->standby) {
-        in->standby = false;
-        in->standby_exit_time = current_time;
-        in->standby_frames_read = 0;
-    }
-
-    const int64_t frames_available = current_position - in->standby_position - in->standby_frames_read;
-    assert(frames_available >= 0);
-
-    const size_t frames_wait = ((uint64_t)frames_available > frames) ? 0 : frames - frames_available;
-
-    int64_t sleep_time_us  = frames_wait * 1000000LL /
-                             in_get_sample_rate(&stream->common);
-
-    pthread_mutex_unlock(&in->lock);
-
-    if (sleep_time_us > 0) {
-        usleep(sleep_time_us);
-    }
-
-    pthread_mutex_lock(&in->lock);
-    int read_frames = 0;
-    if (in->standby) {
-        ALOGW("Input put to sleep while read in progress");
-        goto exit;
-    }
-    in->standby_frames_read += frames;
-
-    if (popcount(in->req_config.channel_mask) == 1 &&
-        in->pcm_config.channels == 2) {
-        // Need to resample to mono
-        if (in->stereo_to_mono_buf_size < bytes*2) {
-            in->stereo_to_mono_buf = realloc(in->stereo_to_mono_buf,
-                                             bytes*2);
-            if (!in->stereo_to_mono_buf) {
-                ALOGE("Failed to allocate stereo_to_mono_buff");
-                goto exit;
-            }
-        }
-
-        read_frames = audio_vbuffer_read(&in->buffer, in->stereo_to_mono_buf, frames);
-
-        // Currently only pcm 16 is supported.
-        uint16_t *src = (uint16_t *)in->stereo_to_mono_buf;
-        uint16_t *dst = (uint16_t *)buffer;
-        size_t i;
-        // Resample stereo 16 to mono 16 by dropping one channel.
-        // The stereo stream is interleaved L-R-L-R
-        for (i = 0; i < frames; i++) {
-            *dst = *src;
-            src += 2;
-            dst += 1;
-        }
-    } else {
-        read_frames = audio_vbuffer_read(&in->buffer, buffer, frames);
-    }
-
-exit:
-    read_bytes = read_frames*audio_stream_in_frame_size(stream);
-
-    if (mic_mute) {
-        read_bytes = 0;
-    }
-
-    if (read_bytes < bytes) {
-        memset (&((uint8_t *)buffer)[read_bytes], 0, bytes-read_bytes);
-    }
-
-    pthread_mutex_unlock(&in->lock);
-
-    return bytes;
-}
-
-static uint32_t in_get_input_frames_lost(struct audio_stream_in *stream)
-{
-    return 0;
-}
-
-static int in_get_capture_position(const struct audio_stream_in *stream,
-                                int64_t *frames, int64_t *time)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    pthread_mutex_lock(&in->lock);
-    struct timespec current_time;
-    get_current_input_position(in, frames, &current_time);
-    *time = (current_time.tv_sec * 1000000000LL + current_time.tv_nsec);
-    pthread_mutex_unlock(&in->lock);
-    return 0;
-}
-
-static int in_get_active_microphones(const struct audio_stream_in *stream,
-                                     struct audio_microphone_characteristic_t *mic_array,
-                                     size_t *mic_count)
-{
-    return adev_get_microphones(NULL, mic_array, mic_count);
-}
-
-static int in_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
-{
-    // in_add_audio_effect is a no op
-    return 0;
-}
-
-static int in_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
-{
-    // in_add_audio_effect is a no op
-    return 0;
-}
-
-static int adev_open_output_stream(struct audio_hw_device *dev,
-                                   audio_io_handle_t handle,
-                                   audio_devices_t devices,
-                                   audio_output_flags_t flags,
-                                   struct audio_config *config,
-                                   struct audio_stream_out **stream_out,
-                                   const char *address __unused)
-{
-    struct generic_audio_device *adev = (struct generic_audio_device *)dev;
-    struct generic_stream_out *out;
-    int ret = 0;
-
-    if (refine_output_parameters(&config->sample_rate, &config->format, &config->channel_mask)) {
-        ALOGE("Error opening output stream format %d, channel_mask %04x, sample_rate %u",
-              config->format, config->channel_mask, config->sample_rate);
-        ret = -EINVAL;
-        goto error;
-    }
-
-    out = (struct generic_stream_out *)calloc(1, sizeof(struct generic_stream_out));
-
-    if (!out)
-        return -ENOMEM;
-
-    out->stream.common.get_sample_rate = out_get_sample_rate;
-    out->stream.common.set_sample_rate = out_set_sample_rate;
-    out->stream.common.get_buffer_size = out_get_buffer_size;
-    out->stream.common.get_channels = out_get_channels;
-    out->stream.common.get_format = out_get_format;
-    out->stream.common.set_format = out_set_format;
-    out->stream.common.standby = out_standby;
-    out->stream.common.dump = out_dump;
-    out->stream.common.set_parameters = out_set_parameters;
-    out->stream.common.get_parameters = out_get_parameters;
-    out->stream.common.add_audio_effect = out_add_audio_effect;
-    out->stream.common.remove_audio_effect = out_remove_audio_effect;
-    out->stream.get_latency = out_get_latency;
-    out->stream.set_volume = out_set_volume;
-    out->stream.write = out_write;
-    out->stream.get_render_position = out_get_render_position;
-    out->stream.get_presentation_position = out_get_presentation_position;
-    out->stream.get_next_write_timestamp = out_get_next_write_timestamp;
-
-    out->handle = handle;
-
-    pthread_mutex_init(&out->lock, (const pthread_mutexattr_t *) NULL);
-    out->dev = adev;
-    // Only 1 device is expected despite the argument being named 'devices'
-    out->num_devices = 1;
-    out->devices[0] = devices;
-    memcpy(&out->req_config, config, sizeof(struct audio_config));
-    memcpy(&out->pcm_config, &pcm_config_out, sizeof(struct pcm_config));
-    out->pcm_config.rate = config->sample_rate;
-    out->pcm_config.period_size = out->pcm_config.rate*OUT_PERIOD_MS/1000;
-
-    out->standby = true;
-    out->underrun_position = 0;
-    out->underrun_time.tv_sec = 0;
-    out->underrun_time.tv_nsec = 0;
-    out->last_write_time_us = 0;
-    out->frames_total_buffered = 0;
-    out->frames_written = 0;
-    out->frames_rendered = 0;
-
-    ret = audio_vbuffer_init(&out->buffer,
-                      out->pcm_config.period_size*out->pcm_config.period_count,
-                      out->pcm_config.channels *
-                      pcm_format_to_bits(out->pcm_config.format) >> 3);
-    if (ret == 0) {
-        pthread_cond_init(&out->worker_wake, NULL);
-        out->worker_standby = true;
-        out->worker_exit = false;
-        pthread_create(&out->worker_thread, NULL, out_write_worker, out);
-
-    }
-
-    pthread_mutex_lock(&adev->lock);
-    list_add_tail(&adev->out_streams, &out->stream_node);
-    pthread_mutex_unlock(&adev->lock);
-
-    *stream_out = &out->stream;
-
-error:
-
-    return ret;
-}
-
-// This must be called with adev->lock held.
-struct generic_stream_out *get_stream_out_by_io_handle_l(
-        struct generic_audio_device *adev, audio_io_handle_t handle) {
-    struct listnode *node;
-
-    list_for_each(node, &adev->out_streams) {
-        struct generic_stream_out *out = node_to_item(
-                node, struct generic_stream_out, stream_node);
-        if (out->handle == handle) {
-            return out;
-        }
-    }
-    return NULL;
-}
-
-static void adev_close_output_stream(struct audio_hw_device *dev,
-                                     struct audio_stream_out *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    pthread_mutex_lock(&out->lock);
-    do_out_standby(out);
-
-    out->worker_exit = true;
-    pthread_cond_signal(&out->worker_wake);
-    pthread_mutex_unlock(&out->lock);
-
-    pthread_join(out->worker_thread, NULL);
-    pthread_mutex_destroy(&out->lock);
-    audio_vbuffer_destroy(&out->buffer);
-
-    struct generic_audio_device *adev = (struct generic_audio_device *) dev;
-    pthread_mutex_lock(&adev->lock);
-    list_remove(&out->stream_node);
-    pthread_mutex_unlock(&adev->lock);
-    free(stream);
-}
-
-static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs)
-{
-    return 0;
-}
-
-static char * adev_get_parameters(const struct audio_hw_device *dev,
-                                  const char *keys)
-{
-    return strdup("");
-}
-
-static int adev_get_audio_port(struct audio_hw_device *dev,
-                               struct audio_port *port)
-{
-    return 0;
-}
-
-static int adev_init_check(const struct audio_hw_device *dev)
-{
-    return 0;
-}
-
-static int adev_set_voice_volume(struct audio_hw_device *dev, float volume)
-{
-    // adev_set_voice_volume is a no op (simulates phones)
-    return 0;
-}
-
-static int adev_set_master_volume(struct audio_hw_device *dev, float volume)
-{
-    return -ENOSYS;
-}
-
-static int adev_get_master_volume(struct audio_hw_device *dev, float *volume)
-{
-    return -ENOSYS;
-}
-
-static int adev_set_master_mute(struct audio_hw_device *dev, bool muted)
-{
-    return -ENOSYS;
-}
-
-static int adev_get_master_mute(struct audio_hw_device *dev, bool *muted)
-{
-    return -ENOSYS;
-}
-
-static int adev_set_mode(struct audio_hw_device *dev, audio_mode_t mode)
-{
-    // adev_set_mode is a no op (simulates phones)
-    return 0;
-}
-
-static int adev_set_mic_mute(struct audio_hw_device *dev, bool state)
-{
-    struct generic_audio_device *adev = (struct generic_audio_device *)dev;
-    pthread_mutex_lock(&adev->lock);
-    adev->mic_mute = state;
-    pthread_mutex_unlock(&adev->lock);
-    return 0;
-}
-
-static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state)
-{
-    struct generic_audio_device *adev = (struct generic_audio_device *)dev;
-    pthread_mutex_lock(&adev->lock);
-    *state = adev->mic_mute;
-    pthread_mutex_unlock(&adev->lock);
-    return 0;
-}
-
-
-static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev,
-                                         const struct audio_config *config)
-{
-    return get_input_buffer_size(config->sample_rate, config->format, config->channel_mask);
-}
-
-// This must be called with adev->lock held.
-struct generic_stream_in *get_stream_in_by_io_handle_l(
-        struct generic_audio_device *adev, audio_io_handle_t handle) {
-    struct listnode *node;
-
-    list_for_each(node, &adev->in_streams) {
-        struct generic_stream_in *in = node_to_item(
-                node, struct generic_stream_in, stream_node);
-        if (in->handle == handle) {
-            return in;
-        }
-    }
-    return NULL;
-}
-
-static void adev_close_input_stream(struct audio_hw_device *dev,
-                                   struct audio_stream_in *stream)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    pthread_mutex_lock(&in->lock);
-    do_in_standby(in);
-
-    in->worker_exit = true;
-    pthread_cond_signal(&in->worker_wake);
-    pthread_mutex_unlock(&in->lock);
-    pthread_join(in->worker_thread, NULL);
-
-    if (in->stereo_to_mono_buf != NULL) {
-        free(in->stereo_to_mono_buf);
-        in->stereo_to_mono_buf_size = 0;
-    }
-
-    pthread_mutex_destroy(&in->lock);
-    audio_vbuffer_destroy(&in->buffer);
-
-    struct generic_audio_device *adev = (struct generic_audio_device *) dev;
-    pthread_mutex_lock(&adev->lock);
-    list_remove(&in->stream_node);
-    pthread_mutex_unlock(&adev->lock);
-    free(stream);
-}
-
-
-static int adev_open_input_stream(struct audio_hw_device *dev,
-                                  audio_io_handle_t handle,
-                                  audio_devices_t devices,
-                                  struct audio_config *config,
-                                  struct audio_stream_in **stream_in,
-                                  audio_input_flags_t flags __unused,
-                                  const char *address __unused,
-                                  audio_source_t source __unused)
-{
-    struct generic_audio_device *adev = (struct generic_audio_device *)dev;
-    struct generic_stream_in *in;
-    int ret = 0;
-    uint32_t orig_sample_rate = config->sample_rate;
-    audio_format_t orig_audio_format = config->format;
-    audio_channel_mask_t orig_channel_mask = config->channel_mask;
-    if (refine_input_parameters(&config->sample_rate, &config->format, &config->channel_mask)) {
-        ALOGE("Error opening input stream format %d, channel_mask %04x, sample_rate %u",
-              orig_audio_format, orig_channel_mask, orig_sample_rate);
-        ret = -EINVAL;
-        goto error;
-    }
-
-    in = (struct generic_stream_in *)calloc(1, sizeof(struct generic_stream_in));
-    if (!in) {
-        ret = -ENOMEM;
-        goto error;
-    }
-
-    in->stream.common.get_sample_rate = in_get_sample_rate;
-    in->stream.common.set_sample_rate = in_set_sample_rate;         // no op
-    in->stream.common.get_buffer_size = in_get_buffer_size;
-    in->stream.common.get_channels = in_get_channels;
-    in->stream.common.get_format = in_get_format;
-    in->stream.common.set_format = in_set_format;                   // no op
-    in->stream.common.standby = in_standby;
-    in->stream.common.dump = in_dump;
-    in->stream.common.set_parameters = in_set_parameters;
-    in->stream.common.get_parameters = in_get_parameters;
-    in->stream.common.add_audio_effect = in_add_audio_effect;       // no op
-    in->stream.common.remove_audio_effect = in_remove_audio_effect; // no op
-    in->stream.set_gain = in_set_gain;                              // no op
-    in->stream.read = in_read;
-    in->stream.get_input_frames_lost = in_get_input_frames_lost;    // no op
-    in->stream.get_capture_position = in_get_capture_position;
-    in->stream.get_active_microphones = in_get_active_microphones;
-
-    pthread_mutex_init(&in->lock, (const pthread_mutexattr_t *) NULL);
-    in->dev = adev;
-    in->device = devices;
-    memcpy(&in->req_config, config, sizeof(struct audio_config));
-    memcpy(&in->pcm_config, &pcm_config_in, sizeof(struct pcm_config));
-    in->pcm_config.rate = config->sample_rate;
-    in->pcm_config.period_size = in->pcm_config.rate*IN_PERIOD_MS/1000;
-
-    in->stereo_to_mono_buf = NULL;
-    in->stereo_to_mono_buf_size = 0;
-
-    in->standby = true;
-    in->standby_position = 0;
-    in->standby_exit_time.tv_sec = 0;
-    in->standby_exit_time.tv_nsec = 0;
-    in->standby_frames_read = 0;
-
-    ret = audio_vbuffer_init(&in->buffer,
-                      in->pcm_config.period_size*in->pcm_config.period_count,
-                      in->pcm_config.channels *
-                      pcm_format_to_bits(in->pcm_config.format) >> 3);
-    if (ret == 0) {
-        pthread_cond_init(&in->worker_wake, NULL);
-        in->worker_standby = true;
-        in->worker_exit = false;
-        pthread_create(&in->worker_thread, NULL, in_read_worker, in);
-    }
-    in->handle = handle;
-
-    pthread_mutex_lock(&adev->lock);
-    list_add_tail(&adev->in_streams, &in->stream_node);
-    pthread_mutex_unlock(&adev->lock);
-
-    *stream_in = &in->stream;
-
-error:
-    return ret;
-}
-
-
-static int adev_dump(const audio_hw_device_t *dev, int fd)
-{
-    return 0;
-}
-
-static int adev_get_microphones(const audio_hw_device_t *dev,
-                                struct audio_microphone_characteristic_t *mic_array,
-                                size_t *mic_count)
-{
-    if (mic_count == NULL) {
-        return -ENOSYS;
-    }
-
-    if (*mic_count == 0) {
-        *mic_count = 0;
-        return 0;
-    }
-
-    if (mic_array == NULL) {
-        return -ENOSYS;
-    }
-
-    strncpy(mic_array->device_id, "mic_goldfish", AUDIO_MICROPHONE_ID_MAX_LEN - 1);
-    mic_array->device = AUDIO_DEVICE_IN_BUILTIN_MIC;
-    strncpy(mic_array->address, AUDIO_BOTTOM_MICROPHONE_ADDRESS,
-            AUDIO_DEVICE_MAX_ADDRESS_LEN - 1);
-    memset(mic_array->channel_mapping, AUDIO_MICROPHONE_CHANNEL_MAPPING_UNUSED,
-           sizeof(mic_array->channel_mapping));
-    mic_array->location = AUDIO_MICROPHONE_LOCATION_UNKNOWN;
-    mic_array->group = 0;
-    mic_array->index_in_the_group = 0;
-    mic_array->sensitivity = AUDIO_MICROPHONE_SENSITIVITY_UNKNOWN;
-    mic_array->max_spl = AUDIO_MICROPHONE_SPL_UNKNOWN;
-    mic_array->min_spl = AUDIO_MICROPHONE_SPL_UNKNOWN;
-    mic_array->directionality = AUDIO_MICROPHONE_DIRECTIONALITY_UNKNOWN;
-    mic_array->num_frequency_responses = 0;
-    mic_array->geometric_location.x = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-    mic_array->geometric_location.y = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-    mic_array->geometric_location.z = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-    mic_array->orientation.x = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-    mic_array->orientation.y = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-    mic_array->orientation.z = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-
-    *mic_count = 0;
-    return 0;
-}
-
-static int adev_create_audio_patch(struct audio_hw_device *dev,
-                                   unsigned int num_sources,
-                                   const struct audio_port_config *sources,
-                                   unsigned int num_sinks,
-                                   const struct audio_port_config *sinks,
-                                   audio_patch_handle_t *handle) {
-    if (num_sources != 1 || num_sinks == 0 || num_sinks > AUDIO_PATCH_PORTS_MAX) {
-        return -EINVAL;
-    }
-
-    if (sources[0].type == AUDIO_PORT_TYPE_DEVICE) {
-        // If source is a device, the number of sinks should be 1.
-        if (num_sinks != 1 || sinks[0].type != AUDIO_PORT_TYPE_MIX) {
-            return -EINVAL;
-        }
-    } else if (sources[0].type == AUDIO_PORT_TYPE_MIX) {
-        // If source is a mix, all sinks should be device.
-        for (unsigned int i = 0; i < num_sinks; i++) {
-            if (sinks[i].type != AUDIO_PORT_TYPE_DEVICE) {
-                ALOGE("%s() invalid sink type %#x for mix source", __func__, sinks[i].type);
-                return -EINVAL;
-            }
-        }
-    } else {
-        // All other cases are invalid.
-        return -EINVAL;
-    }
-
-    struct generic_audio_device* adev = (struct generic_audio_device*) dev;
-    int ret = 0;
-    bool generatedPatchHandle = false;
-    pthread_mutex_lock(&adev->lock);
-    if (*handle == AUDIO_PATCH_HANDLE_NONE) {
-        *handle = ++adev->next_patch_handle;
-        generatedPatchHandle = true;
-    }
-
-    // Only handle patches for mix->devices and device->mix case.
-    if (sources[0].type == AUDIO_PORT_TYPE_DEVICE) {
-        struct generic_stream_in *in =
-                get_stream_in_by_io_handle_l(adev, sinks[0].ext.mix.handle);
-        if (in == NULL) {
-            ALOGE("%s()can not find stream with handle(%d)", __func__, sources[0].ext.mix.handle);
-            ret = -EINVAL;
-            goto error;
-        }
-
-        // Check if the patch handle match the recorded one if a valid patch handle is passed.
-        if (!generatedPatchHandle && in->patch_handle != *handle) {
-            ALOGE("%s() the patch handle(%d) does not match recorded one(%d) for stream "
-                  "with handle(%d) when creating audio patch for device->mix",
-                  __func__, *handle, in->patch_handle, in->handle);
-            ret = -EINVAL;
-            goto error;
-        }
-        pthread_mutex_lock(&in->lock);
-        in->device = sources[0].ext.device.type;
-        pthread_mutex_unlock(&in->lock);
-        in->patch_handle = *handle;
-    } else {
-        struct generic_stream_out *out =
-                get_stream_out_by_io_handle_l(adev, sources[0].ext.mix.handle);
-        if (out == NULL) {
-            ALOGE("%s()can not find stream with handle(%d)", __func__, sources[0].ext.mix.handle);
-            ret = -EINVAL;
-            goto error;
-        }
-
-        // Check if the patch handle match the recorded one if a valid patch handle is passed.
-        if (!generatedPatchHandle && out->patch_handle != *handle) {
-            ALOGE("%s() the patch handle(%d) does not match recorded one(%d) for stream "
-                  "with handle(%d) when creating audio patch for mix->device",
-                  __func__, *handle, out->patch_handle, out->handle);
-            ret = -EINVAL;
-            pthread_mutex_unlock(&out->lock);
-            goto error;
-        }
-        pthread_mutex_lock(&out->lock);
-        for (out->num_devices = 0; out->num_devices < num_sinks; out->num_devices++) {
-            out->devices[out->num_devices] = sinks[out->num_devices].ext.device.type;
-        }
-        pthread_mutex_unlock(&out->lock);
-        out->patch_handle = *handle;
-    }
-
-error:
-    if (ret != 0 && generatedPatchHandle) {
-        *handle = AUDIO_PATCH_HANDLE_NONE;
-    }
-    pthread_mutex_unlock(&adev->lock);
-    return 0;
-}
-
-// This must be called with adev->lock held.
-struct generic_stream_out *get_stream_out_by_patch_handle_l(
-        struct generic_audio_device *adev, audio_patch_handle_t patch_handle) {
-    struct listnode *node;
-
-    list_for_each(node, &adev->out_streams) {
-        struct generic_stream_out *out = node_to_item(
-                node, struct generic_stream_out, stream_node);
-        if (out->patch_handle == patch_handle) {
-            return out;
-        }
-    }
-    return NULL;
-}
-
-// This must be called with adev->lock held.
-struct generic_stream_in *get_stream_in_by_patch_handle_l(
-        struct generic_audio_device *adev, audio_patch_handle_t patch_handle) {
-    struct listnode *node;
-
-    list_for_each(node, &adev->in_streams) {
-        struct generic_stream_in *in = node_to_item(
-                node, struct generic_stream_in, stream_node);
-        if (in->patch_handle == patch_handle) {
-            return in;
-        }
-    }
-    return NULL;
-}
-
-static int adev_release_audio_patch(struct audio_hw_device *dev,
-                                    audio_patch_handle_t patch_handle) {
-    struct generic_audio_device *adev = (struct generic_audio_device *) dev;
-
-    pthread_mutex_lock(&adev->lock);
-    struct generic_stream_out *out = get_stream_out_by_patch_handle_l(adev, patch_handle);
-    if (out != NULL) {
-        pthread_mutex_lock(&out->lock);
-        out->num_devices = 0;
-        memset(out->devices, 0, sizeof(out->devices));
-        pthread_mutex_unlock(&out->lock);
-        out->patch_handle = AUDIO_PATCH_HANDLE_NONE;
-        pthread_mutex_unlock(&adev->lock);
-        return 0;
-    }
-    struct generic_stream_in *in = get_stream_in_by_patch_handle_l(adev, patch_handle);
-    if (in != NULL) {
-        pthread_mutex_lock(&in->lock);
-        in->device = AUDIO_DEVICE_NONE;
-        pthread_mutex_unlock(&in->lock);
-        in->patch_handle = AUDIO_PATCH_HANDLE_NONE;
-        pthread_mutex_unlock(&adev->lock);
-        return 0;
-    }
-
-    pthread_mutex_unlock(&adev->lock);
-    ALOGW("%s() cannot find stream for patch handle: %d", __func__, patch_handle);
-    return -EINVAL;
-}
-
-static int adev_close(hw_device_t *dev)
-{
-    struct generic_audio_device *adev = (struct generic_audio_device *)dev;
-    int ret = 0;
-    if (!adev)
-        return 0;
-
-    pthread_mutex_lock(&adev_init_lock);
-
-    if (audio_device_ref_count == 0) {
-        ALOGE("adev_close called when ref_count 0");
-        ret = -EINVAL;
-        goto error;
-    }
-
-    if ((--audio_device_ref_count) == 0) {
-        if (adev->mixer) {
-            mixer_close(adev->mixer);
-        }
-        free(adev);
-    }
-
-error:
-    pthread_mutex_unlock(&adev_init_lock);
-    return ret;
-}
-
-static int adev_open(const hw_module_t* module, const char* name,
-                     hw_device_t** device)
-{
-    static struct generic_audio_device *adev;
-
-    if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0)
-        return -EINVAL;
-
-    pthread_mutex_lock(&adev_init_lock);
-    if (audio_device_ref_count != 0) {
-        *device = &adev->device.common;
-        audio_device_ref_count++;
-        ALOGV("%s: returning existing instance of adev", __func__);
-        ALOGV("%s: exit", __func__);
-        goto unlock;
-    }
-    adev = calloc(1, sizeof(struct generic_audio_device));
-
-    pthread_mutex_init(&adev->lock, (const pthread_mutexattr_t *) NULL);
-
-    adev->device.common.tag = HARDWARE_DEVICE_TAG;
-    adev->device.common.version = AUDIO_DEVICE_API_VERSION_3_0;
-    adev->device.common.module = (struct hw_module_t *) module;
-    adev->device.common.close = adev_close;
-
-    adev->device.init_check = adev_init_check;               // no op
-    adev->device.set_voice_volume = adev_set_voice_volume;   // no op
-    adev->device.set_master_volume = adev_set_master_volume; // no op
-    adev->device.get_master_volume = adev_get_master_volume; // no op
-    adev->device.set_master_mute = adev_set_master_mute;     // no op
-    adev->device.get_master_mute = adev_get_master_mute;     // no op
-    adev->device.set_mode = adev_set_mode;                   // no op
-    adev->device.set_mic_mute = adev_set_mic_mute;
-    adev->device.get_mic_mute = adev_get_mic_mute;
-    adev->device.set_parameters = adev_set_parameters;       // no op
-    adev->device.get_parameters = adev_get_parameters;       // no op
-    adev->device.get_audio_port = adev_get_audio_port;       // no op
-    adev->device.get_input_buffer_size = adev_get_input_buffer_size;
-    adev->device.open_output_stream = adev_open_output_stream;
-    adev->device.close_output_stream = adev_close_output_stream;
-    adev->device.open_input_stream = adev_open_input_stream;
-    adev->device.close_input_stream = adev_close_input_stream;
-    adev->device.dump = adev_dump;
-    adev->device.get_microphones = adev_get_microphones;
-    adev->device.create_audio_patch = adev_create_audio_patch;
-    adev->device.release_audio_patch = adev_release_audio_patch;
-
-    *device = &adev->device.common;
-
-    adev->next_patch_handle = AUDIO_PATCH_HANDLE_NONE;
-    list_init(&adev->out_streams);
-    list_init(&adev->in_streams);
-
-    adev->mixer = mixer_open(PCM_CARD);
-    struct mixer_ctl *ctl;
-
-    // Set default mixer ctls
-    // Enable channels and set volume
-    for (int i = 0; i < (int)mixer_get_num_ctls(adev->mixer); i++) {
-        ctl = mixer_get_ctl(adev->mixer, i);
-        ALOGD("mixer %d name %s", i, mixer_ctl_get_name(ctl));
-        if (!strcmp(mixer_ctl_get_name(ctl), "Master Playback Volume") ||
-            !strcmp(mixer_ctl_get_name(ctl), "Capture Volume")) {
-            for (int z = 0; z < (int)mixer_ctl_get_num_values(ctl); z++) {
-                ALOGD("set ctl %d to %d", z, 100);
-                mixer_ctl_set_percent(ctl, z, 100);
-            }
-            continue;
-        }
-        if (!strcmp(mixer_ctl_get_name(ctl), "Master Playback Switch") ||
-            !strcmp(mixer_ctl_get_name(ctl), "Capture Switch")) {
-            for (int z = 0; z < (int)mixer_ctl_get_num_values(ctl); z++) {
-                ALOGD("set ctl %d to %d", z, 1);
-                mixer_ctl_set_value(ctl, z, 1);
-            }
-            continue;
-        }
-    }
-
-    audio_device_ref_count++;
-
-unlock:
-    pthread_mutex_unlock(&adev_init_lock);
-    return 0;
-}
-
-static struct hw_module_methods_t hal_module_methods = {
-    .open = adev_open,
-};
-
-struct audio_module HAL_MODULE_INFO_SYM = {
-    .common = {
-        .tag = HARDWARE_MODULE_TAG,
-        .module_api_version = AUDIO_MODULE_API_VERSION_0_1,
-        .hal_api_version = HARDWARE_HAL_API_VERSION,
-        .id = AUDIO_HARDWARE_MODULE_ID,
-        .name = "Generic audio HW HAL",
-        .author = "The Android Open Source Project",
-        .methods = &hal_module_methods,
-    },
-};
diff --git a/guest/hals/bt/OWNERS b/guest/hals/bt/OWNERS
index e8a4a00..e791d83 100644
--- a/guest/hals/bt/OWNERS
+++ b/guest/hals/bt/OWNERS
@@ -1,2 +1,3 @@
+include device/google/cuttlefish:/OWNERS
 include platform/system/bt:/OWNERS
 jeongik@google.com
\ No newline at end of file
diff --git a/guest/hals/camera/Android.bp b/guest/hals/camera/Android.bp
new file mode 100644
index 0000000..76212ba
--- /dev/null
+++ b/guest/hals/camera/Android.bp
@@ -0,0 +1,83 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "android.hardware.camera.provider@2.6-external-vsock-service",
+    defaults: ["hidl_defaults"],
+    proprietary: true,
+    relative_install_path: "hw",
+    srcs: ["external-service.cpp"],
+    compile_multilib: "first",
+    init_rc: ["android.hardware.camera.provider@2.6-external-vsock-service.rc"],
+    shared_libs: [
+        "android.hardware.camera.provider@2.6",
+        "libbinder",
+        "libhidlbase",
+        "liblog",
+        "libutils",
+    ],
+}
+
+cc_library_shared {
+    name: "android.hardware.camera.provider@2.6-impl-cuttlefish",
+    defaults: ["hidl_defaults"],
+    proprietary: true,
+    relative_install_path: "hw",
+    srcs: [
+        "vsock_camera_provider_2_6.cpp",
+        "vsock_camera_device_3_4.cpp",
+        "vsock_camera_device_session_3_4.cpp",
+        "vsock_camera_metadata.cpp",
+        "vsock_camera_server.cpp",
+        "vsock_frame_provider.cpp",
+        "cached_stream_buffer.cpp",
+        "stream_buffer_cache.cpp",
+    ],
+    shared_libs: [
+        "android.hardware.camera.common@1.0",
+        "android.hardware.camera.device@1.0",
+        "android.hardware.camera.device@3.2",
+        "android.hardware.camera.device@3.3",
+        "android.hardware.camera.device@3.4",
+        "android.hardware.camera.device@3.5",
+        "android.hardware.camera.provider@2.4",
+        "android.hardware.camera.provider@2.5",
+        "android.hardware.camera.provider@2.6",
+        "android.hardware.camera.provider@2.4-external",
+        "android.hardware.camera.provider@2.4-legacy",
+        "android.hardware.graphics.mapper@2.0",
+        "android.hardware.graphics.mapper@3.0",
+        "android.hardware.graphics.mapper@4.0",
+        "android.hidl.allocator@1.0",
+        "android.hidl.memory@1.0",
+        "camera.device@1.0-impl",
+        "camera.device@3.2-impl",
+        "camera.device@3.3-impl",
+        "camera.device@3.4-impl",
+        "libcamera_metadata",
+        "libcutils",
+        "libhardware",
+        "libhidlbase",
+        "liblog",
+        "libutils",
+        "libvsock_utils",
+        "libcuttlefish_fs",
+        "libjsoncpp",
+        "libyuv",
+        "libsync",
+        "libfmq",
+        "libgralloctypes",
+    ],
+    header_libs: [
+        "camera.device@3.4-external-impl_headers",
+        "camera.device@3.4-impl_headers",
+        "camera.device@3.5-external-impl_headers",
+        "camera.device@3.5-impl_headers",
+    ],
+    static_libs: [
+        "android.hardware.camera.common@1.0-helper",
+    ],
+    include_dirs: ["device/google/cuttlefish"],
+    export_include_dirs: ["."],
+}
diff --git a/guest/hals/camera/android.hardware.camera.provider@2.6-external-vsock-service.rc b/guest/hals/camera/android.hardware.camera.provider@2.6-external-vsock-service.rc
new file mode 100644
index 0000000..72c324b
--- /dev/null
+++ b/guest/hals/camera/android.hardware.camera.provider@2.6-external-vsock-service.rc
@@ -0,0 +1,10 @@
+service vendor.camera-provider-2-6-ext /vendor/bin/hw/android.hardware.camera.provider@2.6-external-vsock-service
+    interface android.hardware.camera.provider@2.4::ICameraProvider external/0
+    interface android.hardware.camera.provider@2.5::ICameraProvider external/0
+    interface android.hardware.camera.provider@2.6::ICameraProvider external/0
+    class hal
+    user cameraserver
+    group audio camera input drmrpc
+    ioprio rt 4
+    capabilities SYS_NICE
+    task_profiles CameraServiceCapacity MaxPerformance
\ No newline at end of file
diff --git a/guest/hals/camera/cached_stream_buffer.cpp b/guest/hals/camera/cached_stream_buffer.cpp
new file mode 100644
index 0000000..ff692c7
--- /dev/null
+++ b/guest/hals/camera/cached_stream_buffer.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "CachedStreamBuffer"
+#include "cached_stream_buffer.h"
+#include <hardware/gralloc.h>
+#include <log/log.h>
+#include <sync/sync.h>
+
+namespace android::hardware::camera::device::V3_4::implementation {
+
+namespace {
+HandleImporter g_importer;
+}
+
+ReleaseFence::ReleaseFence(int fence_fd) : handle_(nullptr) {
+  if (fence_fd >= 0) {
+    handle_ = native_handle_create(/*numFds*/ 1, /*numInts*/ 0);
+    handle_->data[0] = fence_fd;
+  }
+}
+
+ReleaseFence::~ReleaseFence() {
+  if (handle_ != nullptr) {
+    native_handle_close(handle_);
+    native_handle_delete(handle_);
+  }
+}
+
+CachedStreamBuffer::CachedStreamBuffer()
+    : buffer_(nullptr), buffer_id_(0), stream_id_(0), acquire_fence_(-1) {}
+
+CachedStreamBuffer::CachedStreamBuffer(const StreamBuffer& buffer)
+    : buffer_(buffer.buffer.getNativeHandle()),
+      buffer_id_(buffer.bufferId),
+      stream_id_(buffer.streamId),
+      acquire_fence_(-1) {
+  g_importer.importBuffer(buffer_);
+  g_importer.importFence(buffer.acquireFence, acquire_fence_);
+}
+
+CachedStreamBuffer::CachedStreamBuffer(CachedStreamBuffer&& from) noexcept {
+  buffer_ = from.buffer_;
+  buffer_id_ = from.buffer_id_;
+  stream_id_ = from.stream_id_;
+  acquire_fence_ = from.acquire_fence_;
+  from.acquire_fence_ = -1;
+  from.buffer_ = nullptr;
+}
+
+CachedStreamBuffer& CachedStreamBuffer::operator=(
+    CachedStreamBuffer&& from) noexcept {
+  if (this != &from) {
+    buffer_ = from.buffer_;
+    buffer_id_ = from.buffer_id_;
+    stream_id_ = from.stream_id_;
+    acquire_fence_ = from.acquire_fence_;
+    from.acquire_fence_ = -1;
+    from.buffer_ = nullptr;
+  }
+  return *this;
+}
+
+CachedStreamBuffer::~CachedStreamBuffer() {
+  if (buffer_ != nullptr) {
+    g_importer.freeBuffer(buffer_);
+  }
+  g_importer.closeFence(acquire_fence_);
+}
+
+void CachedStreamBuffer::importFence(const native_handle_t* fence_handle) {
+  g_importer.closeFence(acquire_fence_);
+  g_importer.importFence(fence_handle, acquire_fence_);
+}
+
+YCbCrLayout CachedStreamBuffer::acquireAsYUV(int32_t width, int32_t height,
+                                             int timeout_ms) {
+  if (acquire_fence_ >= 0) {
+    if (sync_wait(acquire_fence_, timeout_ms)) {
+      ALOGW("%s: timeout while waiting acquire fence", __FUNCTION__);
+      return {};
+    } else {
+      ::close(acquire_fence_);
+      acquire_fence_ = -1;
+    }
+  }
+  IMapper::Rect region{0, 0, width, height};
+  return g_importer.lockYCbCr(buffer_, GRALLOC_USAGE_SW_WRITE_OFTEN, region);
+}
+
+void* CachedStreamBuffer::acquireAsBlob(int32_t size, int timeout_ms) {
+  if (acquire_fence_ >= 0) {
+    if (sync_wait(acquire_fence_, timeout_ms)) {
+      return nullptr;
+    } else {
+      ::close(acquire_fence_);
+      acquire_fence_ = -1;
+    }
+  }
+  return g_importer.lock(buffer_, GRALLOC_USAGE_SW_WRITE_OFTEN, size);
+}
+
+int CachedStreamBuffer::release() { return g_importer.unlock(buffer_); }
+
+}  // namespace android::hardware::camera::device::V3_4::implementation
diff --git a/guest/hals/camera/cached_stream_buffer.h b/guest/hals/camera/cached_stream_buffer.h
new file mode 100644
index 0000000..09a4047
--- /dev/null
+++ b/guest/hals/camera/cached_stream_buffer.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+#pragma once
+#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
+#include "HandleImporter.h"
+
+namespace android::hardware::camera::device::V3_4::implementation {
+
+using ::android::hardware::camera::common::V1_0::helper::HandleImporter;
+using ::android::hardware::camera::device::V3_2::StreamBuffer;
+
+// Small wrapper for allocating/freeing native handles
+class ReleaseFence {
+ public:
+  ReleaseFence(int fence_fd);
+  ~ReleaseFence();
+
+  native_handle_t* handle() const { return handle_; }
+
+ private:
+  native_handle_t* handle_;
+};
+
+// CachedStreamBuffer holds a buffer of camera3 stream.
+class CachedStreamBuffer {
+ public:
+  CachedStreamBuffer();
+  CachedStreamBuffer(const StreamBuffer& buffer);
+  // Not copyable
+  CachedStreamBuffer(const CachedStreamBuffer&) = delete;
+  CachedStreamBuffer& operator=(const CachedStreamBuffer&) = delete;
+  // ...but movable
+  CachedStreamBuffer(CachedStreamBuffer&& from) noexcept;
+  CachedStreamBuffer& operator=(CachedStreamBuffer&& from) noexcept;
+
+  ~CachedStreamBuffer();
+
+  bool valid() const { return buffer_ != nullptr; }
+  uint64_t bufferId() const { return buffer_id_; }
+  int32_t streamId() const { return stream_id_; }
+  int acquireFence() const { return acquire_fence_; }
+
+  void importFence(const native_handle_t* fence_handle);
+  // Acquire methods wait first on acquire fence and then return pointers to
+  // data. Data is nullptr if the wait timed out
+  YCbCrLayout acquireAsYUV(int32_t width, int32_t height, int timeout_ms);
+  void* acquireAsBlob(int32_t size, int timeout_ms);
+  int release();
+
+ private:
+  buffer_handle_t buffer_;
+  uint64_t buffer_id_;
+  int32_t stream_id_;
+  int acquire_fence_;
+};
+
+}  // namespace android::hardware::camera::device::V3_4::implementation
diff --git a/guest/hals/camera/external-service.cpp b/guest/hals/camera/external-service.cpp
new file mode 100644
index 0000000..bab6371
--- /dev/null
+++ b/guest/hals/camera/external-service.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "android.hardware.camera.provider@2.6-external-service"
+
+#include <android/hardware/camera/provider/2.6/ICameraProvider.h>
+#include <hidl/LegacySupport.h>
+
+#include <binder/ProcessState.h>
+
+using android::hardware::defaultPassthroughServiceImplementation;
+using android::hardware::camera::provider::V2_6::ICameraProvider;
+
+int main() {
+  ALOGI("External camera provider service is starting.");
+  // The camera HAL may communicate to other vendor components via
+  // /dev/vndbinder
+  android::ProcessState::initWithDriver("/dev/vndbinder");
+  return defaultPassthroughServiceImplementation<ICameraProvider>(
+      "external/0", /*maxThreads*/ 6);
+}
diff --git a/guest/hals/camera/manifest.xml b/guest/hals/camera/manifest.xml
new file mode 100644
index 0000000..a798060
--- /dev/null
+++ b/guest/hals/camera/manifest.xml
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+<manifest version="1.0" type="device">
+    <hal format="hidl">
+        <name>android.hardware.camera.provider</name>
+        <transport>hwbinder</transport>
+        <version>2.6</version>
+        <interface>
+          <name>ICameraProvider</name>
+          <instance>external/0</instance>
+        </interface>
+    </hal>
+</manifest>
\ No newline at end of file
diff --git a/guest/hals/camera/stream_buffer_cache.cpp b/guest/hals/camera/stream_buffer_cache.cpp
new file mode 100644
index 0000000..dfa13cb
--- /dev/null
+++ b/guest/hals/camera/stream_buffer_cache.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+#include "stream_buffer_cache.h"
+#include <algorithm>
+
+namespace android::hardware::camera::device::V3_4::implementation {
+
+std::shared_ptr<CachedStreamBuffer> StreamBufferCache::get(uint64_t buffer_id) {
+  auto id_match =
+      [buffer_id](const std::shared_ptr<CachedStreamBuffer>& buffer) {
+        return buffer->bufferId() == buffer_id;
+      };
+  std::lock_guard<std::mutex> lock(mutex_);
+  auto found = std::find_if(cache_.begin(), cache_.end(), id_match);
+  return (found != cache_.end()) ? *found : nullptr;
+}
+
+void StreamBufferCache::remove(uint64_t buffer_id) {
+  auto id_match =
+      [&buffer_id](const std::shared_ptr<CachedStreamBuffer>& buffer) {
+        return buffer->bufferId() == buffer_id;
+      };
+  std::lock_guard<std::mutex> lock(mutex_);
+  cache_.erase(std::remove_if(cache_.begin(), cache_.end(), id_match));
+}
+
+void StreamBufferCache::update(const StreamBuffer& buffer) {
+  auto id = buffer.bufferId;
+  auto id_match = [id](const std::shared_ptr<CachedStreamBuffer>& buffer) {
+    return buffer->bufferId() == id;
+  };
+  std::lock_guard<std::mutex> lock(mutex_);
+  auto found = std::find_if(cache_.begin(), cache_.end(), id_match);
+  if (found == cache_.end()) {
+    cache_.emplace_back(std::make_shared<CachedStreamBuffer>(buffer));
+  } else {
+    (*found)->importFence(buffer.acquireFence);
+  }
+}
+
+void StreamBufferCache::clear() {
+  std::lock_guard<std::mutex> lock(mutex_);
+  cache_.clear();
+}
+
+void StreamBufferCache::removeStreamsExcept(std::set<int32_t> streams_to_keep) {
+  std::lock_guard<std::mutex> lock(mutex_);
+  for (auto it = cache_.begin(); it != cache_.end();) {
+    if (streams_to_keep.count((*it)->streamId()) == 0) {
+      it = cache_.erase(it);
+    } else {
+      it++;
+    }
+  }
+}
+
+}  // namespace android::hardware::camera::device::V3_4::implementation
diff --git a/guest/hals/camera/stream_buffer_cache.h b/guest/hals/camera/stream_buffer_cache.h
new file mode 100644
index 0000000..af57bc4
--- /dev/null
+++ b/guest/hals/camera/stream_buffer_cache.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+#pragma once
+#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
+#include <mutex>
+#include <set>
+#include <vector>
+#include "cached_stream_buffer.h"
+
+namespace android::hardware::camera::device::V3_4::implementation {
+
+class StreamBufferCache {
+ public:
+  std::shared_ptr<CachedStreamBuffer> get(uint64_t buffer_id);
+  void remove(uint64_t buffer_id);
+  void update(const StreamBuffer& buffer);
+  void clear();
+  void removeStreamsExcept(std::set<int32_t> streams_to_keep = {});
+
+ private:
+  std::mutex mutex_;
+  std::vector<std::shared_ptr<CachedStreamBuffer>> cache_;
+};
+
+}  // namespace android::hardware::camera::device::V3_4::implementation
diff --git a/guest/hals/camera/vsock_camera_device_3_4.cpp b/guest/hals/camera/vsock_camera_device_3_4.cpp
new file mode 100644
index 0000000..310a9f8
--- /dev/null
+++ b/guest/hals/camera/vsock_camera_device_3_4.cpp
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VsockCameraDevice"
+//#define LOG_NDEBUG 0
+#include <log/log.h>
+
+#include <algorithm>
+#include <array>
+#include "CameraMetadata.h"
+#include "android-base/macros.h"
+#include "include/convert.h"
+#include "vsock_camera_device_3_4.h"
+#include "vsock_camera_device_session_3_4.h"
+
+namespace android::hardware::camera::device::V3_4::implementation {
+
+VsockCameraDevice::VsockCameraDevice(
+    const std::string& id, const Settings& settings,
+    std::shared_ptr<cuttlefish::VsockConnection> connection)
+    : id_(id),
+      metadata_(settings.width, settings.height, settings.frame_rate),
+      connection_(connection),
+      is_open_(false) {
+  ALOGI("%s", __FUNCTION__);
+}
+
+VsockCameraDevice::~VsockCameraDevice() { ALOGI("%s", __FUNCTION__); }
+
+Return<void> VsockCameraDevice::getResourceCost(
+    ICameraDevice::getResourceCost_cb _hidl_cb) {
+  CameraResourceCost resCost;
+  resCost.resourceCost = 100;
+  _hidl_cb(Status::OK, resCost);
+  return Void();
+}
+
+Return<void> VsockCameraDevice::getCameraCharacteristics(
+    ICameraDevice::getCameraCharacteristics_cb _hidl_cb) {
+  V3_2::CameraMetadata hidl_vec;
+  const camera_metadata_t* metadata_ptr = metadata_.getAndLock();
+  V3_2::implementation::convertToHidl(metadata_ptr, &hidl_vec);
+  _hidl_cb(Status::OK, hidl_vec);
+  metadata_.unlock(metadata_ptr);
+  return Void();
+}
+
+Return<Status> VsockCameraDevice::setTorchMode(TorchMode) {
+  return Status::OPERATION_NOT_SUPPORTED;
+}
+
+Return<void> VsockCameraDevice::open(const sp<ICameraDeviceCallback>& callback,
+                                     ICameraDevice::open_cb _hidl_cb) {
+  if (callback == nullptr) {
+    ALOGE("%s: cannot open camera %s. callback is null!", __FUNCTION__,
+          id_.c_str());
+    _hidl_cb(Status::ILLEGAL_ARGUMENT, nullptr);
+    return Void();
+  }
+
+  bool was_open = is_open_.exchange(true);
+
+  if (was_open) {
+    ALOGE("%s: cannot open an already opened camera!", __FUNCTION__);
+    _hidl_cb(Status::CAMERA_IN_USE, nullptr);
+    return Void();
+  }
+  ALOGI("%s: Initializing device for camera %s", __FUNCTION__, id_.c_str());
+  frame_provider_ = std::make_shared<cuttlefish::VsockFrameProvider>();
+  frame_provider_->start(connection_, metadata_.getPreferredWidth(),
+                         metadata_.getPreferredHeight());
+  session_ = new VsockCameraDeviceSession(metadata_, frame_provider_, callback);
+  _hidl_cb(Status::OK, session_);
+  return Void();
+}
+
+Return<void> VsockCameraDevice::dumpState(
+    const ::android::hardware::hidl_handle& handle) {
+  if (handle.getNativeHandle() == nullptr) {
+    ALOGE("%s: handle must not be null", __FUNCTION__);
+    return Void();
+  }
+  if (handle->numFds != 1 || handle->numInts != 0) {
+    ALOGE("%s: handle must contain 1 FD and 0 integers! Got %d FDs and %d ints",
+          __FUNCTION__, handle->numFds, handle->numInts);
+    return Void();
+  }
+  int fd = handle->data[0];
+  dprintf(fd, "Camera:%s\n", id_.c_str());
+  return Void();
+}
+
+}  // namespace android::hardware::camera::device::V3_4::implementation
diff --git a/guest/hals/camera/vsock_camera_device_3_4.h b/guest/hals/camera/vsock_camera_device_3_4.h
new file mode 100644
index 0000000..9d92991
--- /dev/null
+++ b/guest/hals/camera/vsock_camera_device_3_4.h
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include "CameraMetadata.h"
+
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
+#include <hidl/MQDescriptor.h>
+#include <hidl/Status.h>
+#include "vsock_camera_device_session_3_4.h"
+#include "vsock_camera_metadata.h"
+#include "vsock_connection.h"
+#include "vsock_frame_provider.h"
+
+#include <vector>
+
+namespace android::hardware::camera::device::V3_4::implementation {
+
+using namespace ::android::hardware::camera::device;
+using ::android::sp;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::camera::common::V1_0::CameraResourceCost;
+using ::android::hardware::camera::common::V1_0::Status;
+using ::android::hardware::camera::common::V1_0::TorchMode;
+using ::android::hardware::camera::device::V3_2::ICameraDevice;
+using ::android::hardware::camera::device::V3_2::ICameraDeviceCallback;
+
+class VsockCameraDevice : public ICameraDevice {
+ public:
+  using Settings = struct {
+    int32_t width;
+    int32_t height;
+    double frame_rate;
+  };
+
+  VsockCameraDevice(const std::string& id, const Settings& settings,
+                    std::shared_ptr<cuttlefish::VsockConnection> connection);
+  virtual ~VsockCameraDevice();
+
+  /* Methods from ::android::hardware::camera::device::V3_2::ICameraDevice
+   * follow. */
+  Return<void> getResourceCost(ICameraDevice::getResourceCost_cb _hidl_cb);
+  Return<void> getCameraCharacteristics(
+      ICameraDevice::getCameraCharacteristics_cb _hidl_cb);
+  Return<Status> setTorchMode(TorchMode);
+  Return<void> open(const sp<ICameraDeviceCallback>&, ICameraDevice::open_cb);
+  Return<void> dumpState(const ::android::hardware::hidl_handle&);
+  /* End of Methods from
+   * ::android::hardware::camera::device::V3_2::ICameraDevice */
+
+ private:
+  std::string id_;
+  VsockCameraMetadata metadata_;
+  std::shared_ptr<cuttlefish::VsockConnection> connection_;
+  std::shared_ptr<cuttlefish::VsockFrameProvider> frame_provider_;
+  std::atomic<bool> is_open_;
+  sp<VsockCameraDeviceSession> session_;
+};
+
+}  // namespace android::hardware::camera::device::V3_4::implementation
diff --git a/guest/hals/camera/vsock_camera_device_session_3_4.cpp b/guest/hals/camera/vsock_camera_device_session_3_4.cpp
new file mode 100644
index 0000000..9c0b597
--- /dev/null
+++ b/guest/hals/camera/vsock_camera_device_session_3_4.cpp
@@ -0,0 +1,574 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "VsockCameraDeviceSession"
+#include "vsock_camera_device_session_3_4.h"
+#include <hidl/Status.h>
+#include <include/convert.h>
+#include <inttypes.h>
+#include <libyuv.h>
+#include <log/log.h>
+#include "vsock_camera_metadata.h"
+
+// Partially copied from ExternalCameraDeviceSession
+namespace android::hardware::camera::device::V3_4::implementation {
+
+VsockCameraDeviceSession::VsockCameraDeviceSession(
+    VsockCameraMetadata camera_characteristics,
+    std::shared_ptr<cuttlefish::VsockFrameProvider> frame_provider,
+    const sp<ICameraDeviceCallback>& callback)
+    : camera_characteristics_(camera_characteristics),
+      frame_provider_(frame_provider),
+      callback_(callback) {
+  static constexpr size_t kMsgQueueSize = 256 * 1024;
+  request_queue_ =
+      std::make_unique<MessageQueue<uint8_t, kSynchronizedReadWrite>>(
+          kMsgQueueSize, false);
+  result_queue_ =
+      std::make_shared<MessageQueue<uint8_t, kSynchronizedReadWrite>>(
+          kMsgQueueSize, false);
+  unsigned int timeout_ms = 1000 / camera_characteristics.getPreferredFps();
+  process_requests_ = true;
+  request_processor_ =
+      std::thread([this, timeout_ms] { processRequestLoop(timeout_ms); });
+}
+
+VsockCameraDeviceSession::~VsockCameraDeviceSession() { close(); }
+
+Return<void> VsockCameraDeviceSession::constructDefaultRequestSettings(
+    V3_2::RequestTemplate type,
+    V3_2::ICameraDeviceSession::constructDefaultRequestSettings_cb _hidl_cb) {
+  auto frame_rate = camera_characteristics_.getPreferredFps();
+  auto metadata = VsockCameraRequestMetadata(frame_rate, type);
+  V3_2::CameraMetadata hidl_metadata;
+  Status status = metadata.isValid() ? Status::OK : Status::ILLEGAL_ARGUMENT;
+  if (metadata.isValid()) {
+    camera_metadata_t* metadata_ptr = metadata.release();
+    hidl_metadata.setToExternal((uint8_t*)metadata_ptr,
+                                get_camera_metadata_size(metadata_ptr));
+  }
+  _hidl_cb(status, hidl_metadata);
+  return Void();
+}
+
+Return<void> VsockCameraDeviceSession::getCaptureRequestMetadataQueue(
+    ICameraDeviceSession::getCaptureRequestMetadataQueue_cb _hidl_cb) {
+  _hidl_cb(*request_queue_->getDesc());
+  return Void();
+}
+
+Return<void> VsockCameraDeviceSession::getCaptureResultMetadataQueue(
+    ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb) {
+  _hidl_cb(*result_queue_->getDesc());
+  return Void();
+}
+
+Return<void> VsockCameraDeviceSession::configureStreams(
+    const V3_2::StreamConfiguration& streams,
+    ICameraDeviceSession::configureStreams_cb _hidl_cb) {
+  // common configureStreams operate with v3_2 config and v3_3
+  // streams so we need to "downcast" v3_3 streams to v3_2 streams
+  V3_2::HalStreamConfiguration out_v32;
+  V3_3::HalStreamConfiguration out_v33;
+
+  Status status = configureStreams(streams, &out_v33);
+  size_t size = out_v33.streams.size();
+  out_v32.streams.resize(size);
+  for (size_t i = 0; i < size; i++) {
+    out_v32.streams[i] = out_v33.streams[i].v3_2;
+  }
+  _hidl_cb(status, out_v32);
+  return Void();
+}
+
+Return<void> VsockCameraDeviceSession::configureStreams_3_3(
+    const V3_2::StreamConfiguration& streams,
+    ICameraDeviceSession::configureStreams_3_3_cb _hidl_cb) {
+  V3_3::HalStreamConfiguration out_v33;
+  Status status = configureStreams(streams, &out_v33);
+  _hidl_cb(status, out_v33);
+  return Void();
+}
+
+Return<void> VsockCameraDeviceSession::configureStreams_3_4(
+    const V3_4::StreamConfiguration& requestedConfiguration,
+    ICameraDeviceSession::configureStreams_3_4_cb _hidl_cb) {
+  // common configureStreams operate with v3_2 config and v3_3
+  // streams so we need to "downcast" v3_4 config to v3_2 and
+  // "upcast" v3_3 streams to v3_4 streams
+  V3_2::StreamConfiguration config_v32;
+  V3_3::HalStreamConfiguration out_v33;
+  V3_4::HalStreamConfiguration out_v34;
+
+  config_v32.operationMode = requestedConfiguration.operationMode;
+  config_v32.streams.resize(requestedConfiguration.streams.size());
+  for (size_t i = 0; i < config_v32.streams.size(); i++) {
+    config_v32.streams[i] = requestedConfiguration.streams[i].v3_2;
+  }
+  max_blob_size_ = getBlobSize(requestedConfiguration);
+  Status status = configureStreams(config_v32, &out_v33);
+
+  out_v34.streams.resize(out_v33.streams.size());
+  for (size_t i = 0; i < out_v34.streams.size(); i++) {
+    out_v34.streams[i].v3_3 = out_v33.streams[i];
+  }
+  _hidl_cb(status, out_v34);
+  return Void();
+}
+
+Return<void> VsockCameraDeviceSession::processCaptureRequest(
+    const hidl_vec<CaptureRequest>& requests,
+    const hidl_vec<BufferCache>& cachesToRemove,
+    ICameraDeviceSession::processCaptureRequest_cb _hidl_cb) {
+  updateBufferCaches(cachesToRemove);
+
+  uint32_t count;
+  Status s = Status::OK;
+  for (count = 0; count < requests.size(); count++) {
+    s = processOneCaptureRequest(requests[count]);
+    if (s != Status::OK) {
+      break;
+    }
+  }
+
+  _hidl_cb(s, count);
+  return Void();
+}
+
+Return<void> VsockCameraDeviceSession::processCaptureRequest_3_4(
+    const hidl_vec<V3_4::CaptureRequest>& requests,
+    const hidl_vec<V3_2::BufferCache>& cachesToRemove,
+    ICameraDeviceSession::processCaptureRequest_3_4_cb _hidl_cb) {
+  updateBufferCaches(cachesToRemove);
+
+  uint32_t count;
+  Status s = Status::OK;
+  for (count = 0; count < requests.size(); count++) {
+    s = processOneCaptureRequest(requests[count].v3_2);
+    if (s != Status::OK) {
+      break;
+    }
+  }
+
+  _hidl_cb(s, count);
+  return Void();
+}
+
+Return<Status> VsockCameraDeviceSession::flush() {
+  auto timeout = std::chrono::seconds(1);
+  std::unique_lock<std::mutex> lock(request_mutex_);
+  flushing_requests_ = true;
+  auto is_empty = [this] { return pending_requests_.empty(); };
+  if (!queue_empty_.wait_for(lock, timeout, is_empty)) {
+    ALOGE("Flush timeout - %zu pending requests", pending_requests_.size());
+  }
+  flushing_requests_ = false;
+  return Status::OK;
+}
+
+Return<void> VsockCameraDeviceSession::close() {
+  process_requests_ = false;
+  if (request_processor_.joinable()) {
+    request_processor_.join();
+  }
+  frame_provider_->stop();
+  buffer_cache_.clear();
+  ALOGI("%s", __FUNCTION__);
+  return Void();
+}
+
+using ::android::hardware::graphics::common::V1_0::BufferUsage;
+using ::android::hardware::graphics::common::V1_0::PixelFormat;
+Status VsockCameraDeviceSession::configureStreams(
+    const V3_2::StreamConfiguration& config,
+    V3_3::HalStreamConfiguration* out) {
+  Status status = isStreamConfigurationSupported(config);
+  if (status != Status::OK) {
+    return status;
+  }
+  updateStreamInfo(config);
+  out->streams.resize(config.streams.size());
+  for (size_t i = 0; i < config.streams.size(); i++) {
+    out->streams[i].overrideDataSpace = config.streams[i].dataSpace;
+    out->streams[i].v3_2.id = config.streams[i].id;
+    out->streams[i].v3_2.producerUsage =
+        config.streams[i].usage | BufferUsage::CPU_WRITE_OFTEN;
+    out->streams[i].v3_2.consumerUsage = 0;
+    out->streams[i].v3_2.maxBuffers = 2;
+    out->streams[i].v3_2.overrideFormat =
+        config.streams[i].format == PixelFormat::IMPLEMENTATION_DEFINED
+            ? PixelFormat::YCBCR_420_888
+            : config.streams[i].format;
+  }
+  return Status::OK;
+}
+
+using ::android::hardware::camera::device::V3_2::StreamRotation;
+using ::android::hardware::camera::device::V3_2::StreamType;
+Status VsockCameraDeviceSession::isStreamConfigurationSupported(
+    const V3_2::StreamConfiguration& config) {
+  camera_metadata_entry device_supported_streams = camera_characteristics_.find(
+      ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS);
+  int32_t stall_stream_count = 0;
+  int32_t stream_count = 0;
+  for (const auto& stream : config.streams) {
+    if (stream.rotation != StreamRotation::ROTATION_0) {
+      ALOGE("Unsupported rotation enum value %d", stream.rotation);
+      return Status::ILLEGAL_ARGUMENT;
+    }
+    if (stream.streamType == StreamType::INPUT) {
+      ALOGE("Input stream not supported");
+      return Status::ILLEGAL_ARGUMENT;
+    }
+    bool is_supported = false;
+    // check pixel format and dimensions against camera metadata
+    for (int i = 0; i + 4 <= device_supported_streams.count; i += 4) {
+      auto format =
+          static_cast<PixelFormat>(device_supported_streams.data.i32[i]);
+      int32_t width = device_supported_streams.data.i32[i + 1];
+      int32_t height = device_supported_streams.data.i32[i + 2];
+      if (stream.format == format && stream.width == width &&
+          stream.height == height) {
+        is_supported = true;
+        break;
+      }
+    }
+    if (!is_supported) {
+      ALOGE("Unsupported format %d (%dx%d)", stream.format, stream.width,
+            stream.height);
+      return Status::ILLEGAL_ARGUMENT;
+    }
+    if (stream.format == PixelFormat::BLOB) {
+      stall_stream_count++;
+    } else {
+      stream_count++;
+    }
+  }
+  camera_metadata_entry device_stream_counts =
+      camera_characteristics_.find(ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS);
+  static constexpr auto stream_index = 1;
+  auto expected_stream_count = device_stream_counts.count > stream_index
+                                   ? device_stream_counts.data.i32[stream_index]
+                                   : 0;
+  if (stream_count > expected_stream_count) {
+    ALOGE("Too many processed streams (expect <= %d, got %d)",
+          expected_stream_count, stream_count);
+    return Status::ILLEGAL_ARGUMENT;
+  }
+  static constexpr auto stall_stream_index = 2;
+  expected_stream_count =
+      device_stream_counts.count > stall_stream_index
+          ? device_stream_counts.data.i32[stall_stream_index]
+          : 0;
+  if (stall_stream_count > expected_stream_count) {
+    ALOGE("Too many stall streams (expect <= %d, got %d)",
+          expected_stream_count, stall_stream_count);
+    return Status::ILLEGAL_ARGUMENT;
+  }
+  return Status::OK;
+}
+
+unsigned int VsockCameraDeviceSession::getBlobSize(
+    const V3_4::StreamConfiguration& requestedConfiguration) {
+  camera_metadata_entry jpeg_entry =
+      camera_characteristics_.find(ANDROID_JPEG_MAX_SIZE);
+  unsigned int blob_size = jpeg_entry.count > 0 ? jpeg_entry.data.i32[0] : 0;
+  for (auto& stream : requestedConfiguration.streams) {
+    if (stream.v3_2.format == PixelFormat::BLOB) {
+      if (stream.bufferSize < blob_size) {
+        blob_size = stream.bufferSize;
+      }
+    }
+  }
+  return blob_size;
+}
+
+void VsockCameraDeviceSession::updateBufferCaches(
+    const hidl_vec<BufferCache>& to_remove) {
+  for (auto& cache : to_remove) {
+    buffer_cache_.remove(cache.bufferId);
+  }
+}
+
+void VsockCameraDeviceSession::updateStreamInfo(
+    const V3_2::StreamConfiguration& config) {
+  std::set<int32_t> stream_ids;
+  for (const auto& stream : config.streams) {
+    stream_cache_[stream.id] = stream;
+    stream_ids.emplace(stream.id);
+  }
+  buffer_cache_.removeStreamsExcept(stream_ids);
+}
+
+Status VsockCameraDeviceSession::processOneCaptureRequest(
+    const CaptureRequest& request) {
+  const camera_metadata_t* request_settings = nullptr;
+  V3_2::CameraMetadata hidl_settings;
+  if (request.fmqSettingsSize > 0) {
+    if (!getRequestSettingsFmq(request.fmqSettingsSize, hidl_settings)) {
+      ALOGE("%s: Could not read capture request settings from fmq!",
+            __FUNCTION__);
+      return Status::ILLEGAL_ARGUMENT;
+    } else if (!V3_2::implementation::convertFromHidl(hidl_settings,
+                                                      &request_settings)) {
+      ALOGE("%s: fmq request settings metadata is corrupt!", __FUNCTION__);
+      return Status::ILLEGAL_ARGUMENT;
+    }
+  } else if (!V3_2::implementation::convertFromHidl(request.settings,
+                                                    &request_settings)) {
+    ALOGE("%s: request settings metadata is corrupt!", __FUNCTION__);
+    return Status::ILLEGAL_ARGUMENT;
+  }
+  if (request_settings != nullptr) {
+    // Update request settings. This must happen on first request
+    std::lock_guard<std::mutex> lock(settings_mutex_);
+    latest_request_settings_ = request_settings;
+  } else if (latest_request_settings_.isEmpty()) {
+    ALOGE("%s: Undefined capture request settings!", __FUNCTION__);
+    return Status::ILLEGAL_ARGUMENT;
+  }
+
+  std::vector<uint64_t> buffer_ids;
+  for (size_t i = 0; i < request.outputBuffers.size(); i++) {
+    buffer_cache_.update(request.outputBuffers[i]);
+    buffer_ids.emplace_back(request.outputBuffers[i].bufferId);
+  }
+  std::lock_guard<std::mutex> lock(settings_mutex_);
+  ReadVsockRequest request_to_process = {
+      .buffer_ids = buffer_ids,
+      .frame_number = request.frameNumber,
+      .timestamp = 0,
+      .settings = latest_request_settings_,
+      .buffer_count = static_cast<uint32_t>(buffer_ids.size())};
+  putRequestToQueue(request_to_process);
+  return Status::OK;
+}
+
+bool VsockCameraDeviceSession::getRequestSettingsFmq(
+    uint64_t size, V3_2::CameraMetadata& hidl_settings) {
+  hidl_settings.resize(size);
+  return request_queue_->read(hidl_settings.data(), size);
+}
+
+bool VsockCameraDeviceSession::getRequestFromQueue(ReadVsockRequest& req,
+                                                   unsigned int timeout_ms) {
+  auto timeout = std::chrono::milliseconds(timeout_ms);
+  std::unique_lock<std::mutex> lock(request_mutex_);
+  auto not_empty = [this] { return !pending_requests_.empty(); };
+  if (request_available_.wait_for(lock, timeout, not_empty)) {
+    req = pending_requests_.top();
+    pending_requests_.pop();
+    return true;
+  }
+  queue_empty_.notify_one();
+  return false;
+}
+
+void VsockCameraDeviceSession::putRequestToQueue(
+    const ReadVsockRequest& request) {
+  std::lock_guard<std::mutex> lock(request_mutex_);
+  pending_requests_.push(request);
+  request_available_.notify_one();
+}
+
+void VsockCameraDeviceSession::fillCaptureResult(
+    common::V1_0::helper::CameraMetadata& md, nsecs_t timestamp) {
+  const uint8_t af_state = ANDROID_CONTROL_AF_STATE_INACTIVE;
+  md.update(ANDROID_CONTROL_AF_STATE, &af_state, 1);
+
+  const uint8_t aeState = ANDROID_CONTROL_AE_STATE_CONVERGED;
+  md.update(ANDROID_CONTROL_AE_STATE, &aeState, 1);
+
+  const uint8_t ae_lock = ANDROID_CONTROL_AE_LOCK_OFF;
+  md.update(ANDROID_CONTROL_AE_LOCK, &ae_lock, 1);
+
+  const uint8_t awbState = ANDROID_CONTROL_AWB_STATE_CONVERGED;
+  md.update(ANDROID_CONTROL_AWB_STATE, &awbState, 1);
+
+  const uint8_t awbLock = ANDROID_CONTROL_AWB_LOCK_OFF;
+  md.update(ANDROID_CONTROL_AWB_LOCK, &awbLock, 1);
+
+  const uint8_t flashState = ANDROID_FLASH_STATE_UNAVAILABLE;
+  md.update(ANDROID_FLASH_STATE, &flashState, 1);
+
+  const uint8_t requestPipelineMaxDepth = 4;
+  md.update(ANDROID_REQUEST_PIPELINE_DEPTH, &requestPipelineMaxDepth, 1);
+
+  camera_metadata_entry active_array_size =
+      camera_characteristics_.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+  md.update(ANDROID_SCALER_CROP_REGION, active_array_size.data.i32, 4);
+
+  md.update(ANDROID_SENSOR_TIMESTAMP, &timestamp, 1);
+
+  const uint8_t lensShadingMapMode =
+      ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF;
+  md.update(ANDROID_STATISTICS_LENS_SHADING_MAP_MODE, &lensShadingMapMode, 1);
+
+  const uint8_t sceneFlicker = ANDROID_STATISTICS_SCENE_FLICKER_NONE;
+  md.update(ANDROID_STATISTICS_SCENE_FLICKER, &sceneFlicker, 1);
+}
+
+using ::android::hardware::camera::device::V3_2::MsgType;
+using ::android::hardware::camera::device::V3_2::NotifyMsg;
+void VsockCameraDeviceSession::notifyShutter(uint32_t frame_number,
+                                             nsecs_t timestamp) {
+  NotifyMsg msg;
+  msg.type = MsgType::SHUTTER;
+  msg.msg.shutter.frameNumber = frame_number;
+  msg.msg.shutter.timestamp = timestamp;
+  callback_->notify({msg});
+}
+
+void VsockCameraDeviceSession::notifyError(uint32_t frame_number,
+                                           int32_t stream_id, ErrorCode code) {
+  NotifyMsg msg;
+  msg.type = MsgType::ERROR;
+  msg.msg.error.frameNumber = frame_number;
+  msg.msg.error.errorStreamId = stream_id;
+  msg.msg.error.errorCode = code;
+  callback_->notify({msg});
+}
+
+void VsockCameraDeviceSession::tryWriteFmqResult(V3_2::CaptureResult& result) {
+  result.fmqResultSize = 0;
+  if (result_queue_->availableToWrite() == 0 || result.result.size() == 0) {
+    return;
+  }
+  if (result_queue_->write(result.result.data(), result.result.size())) {
+    result.fmqResultSize = result.result.size();
+    result.result.resize(0);
+  }
+}
+
+using ::android::hardware::camera::device::V3_2::BufferStatus;
+using ::android::hardware::graphics::common::V1_0::PixelFormat;
+void VsockCameraDeviceSession::processRequestLoop(
+    unsigned int wait_timeout_ms) {
+  while (process_requests_.load()) {
+    ReadVsockRequest request;
+    if (!getRequestFromQueue(request, wait_timeout_ms)) {
+      continue;
+    }
+    if (!frame_provider_->isRunning()) {
+      notifyError(request.frame_number, -1, ErrorCode::ERROR_DEVICE);
+      break;
+    }
+    frame_provider_->waitYUVFrame(wait_timeout_ms);
+    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+    if (request.timestamp == 0) {
+      request.timestamp = now;
+      notifyShutter(request.frame_number, request.timestamp);
+    }
+    std::vector<ReleaseFence> release_fences;
+    std::vector<StreamBuffer> result_buffers;
+    std::vector<uint64_t> pending_buffers;
+    bool request_ok = true;
+    for (auto buffer_id : request.buffer_ids) {
+      auto buffer = buffer_cache_.get(buffer_id);
+      auto stream_id = buffer ? buffer->streamId() : -1;
+      if (!buffer || stream_cache_.count(stream_id) == 0) {
+        ALOGE("%s: Invalid buffer", __FUNCTION__);
+        notifyError(request.frame_number, -1, ErrorCode::ERROR_REQUEST);
+        request_ok = false;
+        break;
+      }
+      bool has_result = false;
+      auto stream = stream_cache_[stream_id];
+      if (flushing_requests_.load()) {
+        has_result = false;
+        release_fences.emplace_back(buffer->acquireFence());
+      } else if (stream.format == PixelFormat::YCBCR_420_888 ||
+                 stream.format == PixelFormat::IMPLEMENTATION_DEFINED) {
+        auto dst_yuv =
+            buffer->acquireAsYUV(stream.width, stream.height, wait_timeout_ms);
+        has_result =
+            frame_provider_->copyYUVFrame(stream.width, stream.height, dst_yuv);
+        release_fences.emplace_back(buffer->release());
+      } else if (stream.format == PixelFormat::BLOB) {
+        auto time_elapsed = now - request.timestamp;
+        if (time_elapsed == 0) {
+          frame_provider_->requestJpeg();
+          pending_buffers.push_back(buffer_id);
+          continue;
+        } else if (frame_provider_->jpegPending()) {
+          static constexpr auto kMaxWaitNs = 2000000000L;
+          if (time_elapsed < kMaxWaitNs) {
+            pending_buffers.push_back(buffer_id);
+            continue;
+          }
+          ALOGE("%s: Blob request timed out after %" PRId64 "ms", __FUNCTION__,
+                ns2ms(time_elapsed));
+          frame_provider_->cancelJpegRequest();
+          has_result = false;
+          release_fences.emplace_back(buffer->acquireFence());
+          notifyError(request.frame_number, buffer->streamId(),
+                      ErrorCode::ERROR_BUFFER);
+        } else {
+          ALOGI("%s: Blob ready - capture duration=%" PRId64 "ms", __FUNCTION__,
+                ns2ms(time_elapsed));
+          auto dst_blob =
+              buffer->acquireAsBlob(max_blob_size_, wait_timeout_ms);
+          has_result = frame_provider_->copyJpegData(max_blob_size_, dst_blob);
+          release_fences.emplace_back(buffer->release());
+        }
+      } else {
+        ALOGE("%s: Format %d not supported", __FUNCTION__, stream.format);
+        has_result = false;
+        release_fences.emplace_back(buffer->acquireFence());
+        notifyError(request.frame_number, buffer->streamId(),
+                    ErrorCode::ERROR_BUFFER);
+      }
+      result_buffers.push_back(
+          {.streamId = buffer->streamId(),
+           .bufferId = buffer->bufferId(),
+           .buffer = nullptr,
+           .status = has_result ? BufferStatus::OK : BufferStatus::ERROR,
+           .releaseFence = release_fences.back().handle()});
+    }
+    if (!request_ok) {
+      continue;
+    }
+
+    V3_2::CaptureResult result;
+    bool results_filled = request.settings.exists(ANDROID_SENSOR_TIMESTAMP);
+    if (!results_filled) {
+      fillCaptureResult(request.settings, request.timestamp);
+      const camera_metadata_t* metadata = request.settings.getAndLock();
+      V3_2::implementation::convertToHidl(metadata, &result.result);
+      request.settings.unlock(metadata);
+      tryWriteFmqResult(result);
+    }
+    if (!result_buffers.empty() || !results_filled) {
+      result.frameNumber = request.frame_number;
+      result.partialResult = !results_filled ? 1 : 0;
+      result.inputBuffer.streamId = -1;
+      result.outputBuffers = result_buffers;
+      std::vector<V3_2::CaptureResult> results{result};
+      auto status = callback_->processCaptureResult(results);
+      release_fences.clear();
+      if (!status.isOk()) {
+        ALOGE("%s: processCaptureResult error: %s", __FUNCTION__,
+              status.description().c_str());
+      }
+    }
+    if (!pending_buffers.empty()) {
+      // some buffers still pending
+      request.buffer_ids = pending_buffers;
+      putRequestToQueue(request);
+    }
+  }
+}
+
+}  // namespace android::hardware::camera::device::V3_4::implementation
diff --git a/guest/hals/camera/vsock_camera_device_session_3_4.h b/guest/hals/camera/vsock_camera_device_session_3_4.h
new file mode 100644
index 0000000..2bafa75
--- /dev/null
+++ b/guest/hals/camera/vsock_camera_device_session_3_4.h
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+#pragma once
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
+#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
+#include <android/hardware/graphics/mapper/2.0/IMapper.h>
+#include <fmq/MessageQueue.h>
+#include <queue>
+#include <thread>
+#include "stream_buffer_cache.h"
+#include "vsock_camera_metadata.h"
+#include "vsock_frame_provider.h"
+
+namespace android::hardware::camera::device::V3_4::implementation {
+using ::android::sp;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::kSynchronizedReadWrite;
+using ::android::hardware::MessageQueue;
+using ::android::hardware::Return;
+using ::android::hardware::camera::common::V1_0::Status;
+using ::android::hardware::camera::device::V3_2::BufferCache;
+using ::android::hardware::camera::device::V3_2::CaptureRequest;
+using ::android::hardware::camera::device::V3_2::ErrorCode;
+using ::android::hardware::camera::device::V3_2::ICameraDeviceCallback;
+using ::android::hardware::camera::device::V3_2::RequestTemplate;
+using ::android::hardware::camera::device::V3_2::Stream;
+using ::android::hardware::camera::device::V3_4::ICameraDeviceSession;
+using ::android::hardware::camera::device::V3_4::StreamConfiguration;
+using ::android::hardware::graphics::mapper::V2_0::YCbCrLayout;
+
+class VsockCameraDeviceSession : public ICameraDeviceSession {
+ public:
+  VsockCameraDeviceSession(
+      VsockCameraMetadata camera_characteristics,
+      std::shared_ptr<cuttlefish::VsockFrameProvider> frame_provider,
+      const sp<ICameraDeviceCallback>& callback);
+
+  ~VsockCameraDeviceSession();
+
+  Return<void> constructDefaultRequestSettings(
+      RequestTemplate,
+      ICameraDeviceSession::constructDefaultRequestSettings_cb _hidl_cb);
+
+  Return<void> configureStreams(const V3_2::StreamConfiguration&,
+                                ICameraDeviceSession::configureStreams_cb);
+
+  Return<void> configureStreams_3_3(
+      const V3_2::StreamConfiguration&,
+      ICameraDeviceSession::configureStreams_3_3_cb);
+
+  Return<void> configureStreams_3_4(
+      const V3_4::StreamConfiguration& requestedConfiguration,
+      ICameraDeviceSession::configureStreams_3_4_cb _hidl_cb);
+
+  Return<void> getCaptureRequestMetadataQueue(
+      ICameraDeviceSession::getCaptureRequestMetadataQueue_cb);
+
+  Return<void> getCaptureResultMetadataQueue(
+      ICameraDeviceSession::getCaptureResultMetadataQueue_cb);
+
+  Return<void> processCaptureRequest(
+      const hidl_vec<CaptureRequest>&, const hidl_vec<BufferCache>&,
+      ICameraDeviceSession::processCaptureRequest_cb);
+
+  Return<Status> flush();
+  Return<void> close();
+
+  Return<void> processCaptureRequest_3_4(
+      const hidl_vec<V3_4::CaptureRequest>& requests,
+      const hidl_vec<V3_2::BufferCache>& cachesToRemove,
+      ICameraDeviceSession::processCaptureRequest_3_4_cb _hidl_cb);
+
+ private:
+  struct ReadVsockRequest {
+    std::vector<uint64_t> buffer_ids;
+    uint32_t frame_number;
+    nsecs_t timestamp;
+    common::V1_0::helper::CameraMetadata settings;
+    uint32_t buffer_count;
+  };
+  struct VsockRequestComparator {
+    bool operator()(const ReadVsockRequest& lhs, const ReadVsockRequest& rhs) {
+      return lhs.frame_number > rhs.frame_number;
+    }
+  };
+  void updateBufferCaches(const hidl_vec<BufferCache>& to_remove);
+  Status configureStreams(const V3_2::StreamConfiguration& config,
+                          V3_3::HalStreamConfiguration* out);
+  unsigned int getBlobSize(
+      const V3_4::StreamConfiguration& requestedConfiguration);
+  Status isStreamConfigurationSupported(
+      const V3_2::StreamConfiguration& config);
+  void updateStreamInfo(const V3_2::StreamConfiguration& config);
+  Status processOneCaptureRequest(const CaptureRequest& request);
+  bool getRequestSettingsFmq(uint64_t size,
+                             V3_2::CameraMetadata& hidl_settings);
+  void processRequestLoop(unsigned int timeout);
+  bool getRequestFromQueue(ReadVsockRequest& request, unsigned int timeout_ms);
+  void putRequestToQueue(const ReadVsockRequest& request);
+  void fillCaptureResult(common::V1_0::helper::CameraMetadata& md,
+                         nsecs_t timestamp);
+  void notifyShutter(uint32_t frame_number, nsecs_t timestamp);
+  void notifyError(uint32_t frame_number, int32_t stream_id, ErrorCode code);
+  void tryWriteFmqResult(V3_2::CaptureResult& result);
+  VsockCameraMetadata camera_characteristics_;
+  std::shared_ptr<cuttlefish::VsockFrameProvider> frame_provider_;
+  const sp<ICameraDeviceCallback> callback_;
+  std::unique_ptr<MessageQueue<uint8_t, kSynchronizedReadWrite>> request_queue_;
+  std::shared_ptr<MessageQueue<uint8_t, kSynchronizedReadWrite>> result_queue_;
+  std::mutex settings_mutex_;
+  common::V1_0::helper::CameraMetadata latest_request_settings_;
+
+  StreamBufferCache buffer_cache_;
+  std::map<int32_t, Stream> stream_cache_;
+
+  std::mutex request_mutex_;
+  std::condition_variable request_available_;
+  std::condition_variable queue_empty_;
+  std::priority_queue<ReadVsockRequest, std::vector<ReadVsockRequest>,
+                      VsockRequestComparator>
+      pending_requests_;
+  std::thread request_processor_;
+  std::atomic<bool> process_requests_;
+  std::atomic<bool> flushing_requests_;
+
+  unsigned int max_blob_size_;
+};
+
+}  // namespace android::hardware::camera::device::V3_4::implementation
diff --git a/guest/hals/camera/vsock_camera_metadata.cpp b/guest/hals/camera/vsock_camera_metadata.cpp
new file mode 100644
index 0000000..2e92970
--- /dev/null
+++ b/guest/hals/camera/vsock_camera_metadata.cpp
@@ -0,0 +1,374 @@
+/*
+ * 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.
+ */
+#include "vsock_camera_metadata.h"
+
+#include <hardware/camera3.h>
+#include <utils/misc.h>
+#include <vector>
+
+namespace android::hardware::camera::device::V3_4::implementation {
+
+namespace {
+// Mostly copied from ExternalCameraDevice
+const uint8_t kHardwarelevel = ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL;
+const uint8_t kAberrationMode = ANDROID_COLOR_CORRECTION_ABERRATION_MODE_OFF;
+const uint8_t kAvailableAberrationModes[] = {
+    ANDROID_COLOR_CORRECTION_ABERRATION_MODE_OFF};
+const int32_t kExposureCompensation = 0;
+const uint8_t kAntibandingMode = ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO;
+const int32_t kControlMaxRegions[] = {/*AE*/ 0, /*AWB*/ 0, /*AF*/ 0};
+const uint8_t kVideoStabilizationMode =
+    ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF;
+const uint8_t kAwbAvailableMode = ANDROID_CONTROL_AWB_MODE_AUTO;
+const uint8_t kAePrecaptureTrigger = ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_IDLE;
+const uint8_t kAeAvailableMode = ANDROID_CONTROL_AE_MODE_ON;
+const uint8_t kAvailableFffect = ANDROID_CONTROL_EFFECT_MODE_OFF;
+const uint8_t kControlMode = ANDROID_CONTROL_MODE_AUTO;
+const uint8_t kControlAvailableModes[] = {ANDROID_CONTROL_MODE_OFF,
+                                          ANDROID_CONTROL_MODE_AUTO};
+const uint8_t kEdgeMode = ANDROID_EDGE_MODE_OFF;
+const uint8_t kFlashInfo = ANDROID_FLASH_INFO_AVAILABLE_FALSE;
+const uint8_t kFlashMode = ANDROID_FLASH_MODE_OFF;
+const uint8_t kHotPixelMode = ANDROID_HOT_PIXEL_MODE_OFF;
+const uint8_t kJpegQuality = 90;
+const int32_t kJpegOrientation = 0;
+const int32_t kThumbnailSize[] = {240, 180};
+const int32_t kJpegAvailableThumbnailSizes[] = {0, 0, 240, 180};
+const uint8_t kFocusDistanceCalibration =
+    ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED;
+const uint8_t kOpticalStabilizationMode =
+    ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF;
+const uint8_t kFacing = ANDROID_LENS_FACING_EXTERNAL;
+const float kLensMinFocusDistance = 0.0f;
+const uint8_t kNoiseReductionMode = ANDROID_NOISE_REDUCTION_MODE_OFF;
+const int32_t kPartialResultCount = 1;
+const uint8_t kRequestPipelineMaxDepth = 4;
+const int32_t kRequestMaxNumInputStreams = 0;
+const float kScalerAvailableMaxDigitalZoom[] = {1};
+const uint8_t kCroppingType = ANDROID_SCALER_CROPPING_TYPE_CENTER_ONLY;
+const int32_t kTestPatternMode = ANDROID_SENSOR_TEST_PATTERN_MODE_OFF;
+const int32_t kTestPatternModes[] = {ANDROID_SENSOR_TEST_PATTERN_MODE_OFF};
+const uint8_t kTimestampSource = ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN;
+const int32_t kOrientation = 0;
+const uint8_t kAvailableShadingMode = ANDROID_SHADING_MODE_OFF;
+const uint8_t kFaceDetectMode = ANDROID_STATISTICS_FACE_DETECT_MODE_OFF;
+const int32_t kMaxFaceCount = 0;
+const uint8_t kAvailableHotpixelMode =
+    ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE_OFF;
+const uint8_t kLensShadingMapMode =
+    ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF;
+const int32_t kMaxLatency = ANDROID_SYNC_MAX_LATENCY_UNKNOWN;
+const int32_t kControlAeCompensationRange[] = {0, 0};
+const camera_metadata_rational_t kControlAeCompensationStep[] = {{0, 1}};
+const uint8_t kAfTrigger = ANDROID_CONTROL_AF_TRIGGER_IDLE;
+const uint8_t kAfMode = ANDROID_CONTROL_AF_MODE_OFF;
+const uint8_t kAfAvailableModes[] = {ANDROID_CONTROL_AF_MODE_OFF};
+const uint8_t kAvailableSceneMode = ANDROID_CONTROL_SCENE_MODE_DISABLED;
+const uint8_t kAeLockAvailable = ANDROID_CONTROL_AE_LOCK_AVAILABLE_FALSE;
+const uint8_t kAwbLockAvailable = ANDROID_CONTROL_AWB_LOCK_AVAILABLE_FALSE;
+const int32_t kHalFormats[] = {HAL_PIXEL_FORMAT_BLOB,
+                               HAL_PIXEL_FORMAT_YCbCr_420_888,
+                               HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED};
+const int32_t kRequestMaxNumOutputStreams[] = {
+    /*RAW*/ 0,
+    /*Processed*/ 2,
+    /*Stall*/ 1};
+const uint8_t kAvailableCapabilities[] = {
+    ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE};
+const int32_t kAvailableRequestKeys[] = {
+    ANDROID_COLOR_CORRECTION_ABERRATION_MODE,
+    ANDROID_CONTROL_AE_ANTIBANDING_MODE,
+    ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION,
+    ANDROID_CONTROL_AE_LOCK,
+    ANDROID_CONTROL_AE_MODE,
+    ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
+    ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
+    ANDROID_CONTROL_AF_MODE,
+    ANDROID_CONTROL_AF_TRIGGER,
+    ANDROID_CONTROL_AWB_LOCK,
+    ANDROID_CONTROL_AWB_MODE,
+    ANDROID_CONTROL_CAPTURE_INTENT,
+    ANDROID_CONTROL_EFFECT_MODE,
+    ANDROID_CONTROL_MODE,
+    ANDROID_CONTROL_SCENE_MODE,
+    ANDROID_CONTROL_VIDEO_STABILIZATION_MODE,
+    ANDROID_FLASH_MODE,
+    ANDROID_JPEG_ORIENTATION,
+    ANDROID_JPEG_QUALITY,
+    ANDROID_JPEG_THUMBNAIL_QUALITY,
+    ANDROID_JPEG_THUMBNAIL_SIZE,
+    ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
+    ANDROID_NOISE_REDUCTION_MODE,
+    ANDROID_SCALER_CROP_REGION,
+    ANDROID_SENSOR_TEST_PATTERN_MODE,
+    ANDROID_STATISTICS_FACE_DETECT_MODE,
+    ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE};
+const int32_t kAvailableResultKeys[] = {
+    ANDROID_COLOR_CORRECTION_ABERRATION_MODE,
+    ANDROID_CONTROL_AE_ANTIBANDING_MODE,
+    ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION,
+    ANDROID_CONTROL_AE_LOCK,
+    ANDROID_CONTROL_AE_MODE,
+    ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
+    ANDROID_CONTROL_AE_STATE,
+    ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
+    ANDROID_CONTROL_AF_MODE,
+    ANDROID_CONTROL_AF_STATE,
+    ANDROID_CONTROL_AF_TRIGGER,
+    ANDROID_CONTROL_AWB_LOCK,
+    ANDROID_CONTROL_AWB_MODE,
+    ANDROID_CONTROL_AWB_STATE,
+    ANDROID_CONTROL_CAPTURE_INTENT,
+    ANDROID_CONTROL_EFFECT_MODE,
+    ANDROID_CONTROL_MODE,
+    ANDROID_CONTROL_SCENE_MODE,
+    ANDROID_CONTROL_VIDEO_STABILIZATION_MODE,
+    ANDROID_FLASH_MODE,
+    ANDROID_FLASH_STATE,
+    ANDROID_JPEG_ORIENTATION,
+    ANDROID_JPEG_QUALITY,
+    ANDROID_JPEG_THUMBNAIL_QUALITY,
+    ANDROID_JPEG_THUMBNAIL_SIZE,
+    ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
+    ANDROID_NOISE_REDUCTION_MODE,
+    ANDROID_REQUEST_PIPELINE_DEPTH,
+    ANDROID_SCALER_CROP_REGION,
+    ANDROID_SENSOR_TIMESTAMP,
+    ANDROID_STATISTICS_FACE_DETECT_MODE,
+    ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE,
+    ANDROID_STATISTICS_LENS_SHADING_MAP_MODE,
+    ANDROID_STATISTICS_SCENE_FLICKER};
+const int32_t kAvailableCharacteristicsKeys[] = {
+    ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
+    ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES,
+    ANDROID_CONTROL_AE_AVAILABLE_MODES,
+    ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
+    ANDROID_CONTROL_AE_COMPENSATION_RANGE,
+    ANDROID_CONTROL_AE_COMPENSATION_STEP,
+    ANDROID_CONTROL_AE_LOCK_AVAILABLE,
+    ANDROID_CONTROL_AF_AVAILABLE_MODES,
+    ANDROID_CONTROL_AVAILABLE_EFFECTS,
+    ANDROID_CONTROL_AVAILABLE_MODES,
+    ANDROID_CONTROL_AVAILABLE_SCENE_MODES,
+    ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+    ANDROID_CONTROL_AWB_AVAILABLE_MODES,
+    ANDROID_CONTROL_AWB_LOCK_AVAILABLE,
+    ANDROID_CONTROL_MAX_REGIONS,
+    ANDROID_FLASH_INFO_AVAILABLE,
+    ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL,
+    ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
+    ANDROID_LENS_FACING,
+    ANDROID_LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION,
+    ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION,
+    ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE,
+    ANDROID_NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
+    ANDROID_REQUEST_AVAILABLE_CAPABILITIES,
+    ANDROID_REQUEST_MAX_NUM_INPUT_STREAMS,
+    ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS,
+    ANDROID_REQUEST_PARTIAL_RESULT_COUNT,
+    ANDROID_REQUEST_PIPELINE_MAX_DEPTH,
+    ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM,
+    ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
+    ANDROID_SCALER_CROPPING_TYPE,
+    ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE,
+    ANDROID_SENSOR_INFO_MAX_FRAME_DURATION,
+    ANDROID_SENSOR_INFO_PIXEL_ARRAY_SIZE,
+    ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE,
+    ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE,
+    ANDROID_SENSOR_ORIENTATION,
+    ANDROID_SHADING_AVAILABLE_MODES,
+    ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES,
+    ANDROID_STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES,
+    ANDROID_STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES,
+    ANDROID_STATISTICS_INFO_MAX_FACE_COUNT,
+    ANDROID_SYNC_MAX_LATENCY};
+const std::map<RequestTemplate, uint8_t> kTemplateToIntent = {
+    {RequestTemplate::PREVIEW, ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW},
+    {RequestTemplate::STILL_CAPTURE,
+     ANDROID_CONTROL_CAPTURE_INTENT_STILL_CAPTURE},
+    {RequestTemplate::VIDEO_RECORD,
+     ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_RECORD},
+    {RequestTemplate::VIDEO_SNAPSHOT,
+     ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT},
+};
+}  // namespace
+
+// Constructor sets the default characteristics for vsock camera
+VsockCameraMetadata::VsockCameraMetadata(int32_t width, int32_t height,
+                                         int32_t fps)
+    : width_(width), height_(height), fps_(fps) {
+  update(ANDROID_CONTROL_AE_COMPENSATION_RANGE, kControlAeCompensationRange,
+         NELEM(kControlAeCompensationRange));
+  update(ANDROID_CONTROL_AE_COMPENSATION_STEP, kControlAeCompensationStep,
+         NELEM(kControlAeCompensationStep));
+  update(ANDROID_CONTROL_AF_AVAILABLE_MODES, kAfAvailableModes,
+         NELEM(kAfAvailableModes));
+  update(ANDROID_CONTROL_AVAILABLE_SCENE_MODES, &kAvailableSceneMode, 1);
+  update(ANDROID_CONTROL_AE_LOCK_AVAILABLE, &kAeLockAvailable, 1);
+  update(ANDROID_CONTROL_AWB_LOCK_AVAILABLE, &kAwbLockAvailable, 1);
+  update(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM,
+         kScalerAvailableMaxDigitalZoom, NELEM(kScalerAvailableMaxDigitalZoom));
+  update(ANDROID_REQUEST_AVAILABLE_CAPABILITIES, kAvailableCapabilities,
+         NELEM(kAvailableCapabilities));
+  update(ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL, &kHardwarelevel, 1);
+  update(ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
+         kAvailableAberrationModes, NELEM(kAvailableAberrationModes));
+  update(ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, &kAntibandingMode, 1);
+  update(ANDROID_CONTROL_MAX_REGIONS, kControlMaxRegions,
+         NELEM(kControlMaxRegions));
+  update(ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+         &kVideoStabilizationMode, 1);
+  update(ANDROID_CONTROL_AWB_AVAILABLE_MODES, &kAwbAvailableMode, 1);
+  update(ANDROID_CONTROL_AE_AVAILABLE_MODES, &kAeAvailableMode, 1);
+  update(ANDROID_CONTROL_AVAILABLE_EFFECTS, &kAvailableFffect, 1);
+  update(ANDROID_CONTROL_AVAILABLE_MODES, kControlAvailableModes,
+         NELEM(kControlAvailableModes));
+  update(ANDROID_EDGE_AVAILABLE_EDGE_MODES, &kEdgeMode, 1);
+  update(ANDROID_FLASH_INFO_AVAILABLE, &kFlashInfo, 1);
+  update(ANDROID_HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES, &kHotPixelMode, 1);
+  update(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES, kJpegAvailableThumbnailSizes,
+         NELEM(kJpegAvailableThumbnailSizes));
+  update(ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION,
+         &kFocusDistanceCalibration, 1);
+  update(ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE, &kLensMinFocusDistance, 1);
+  update(ANDROID_LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION,
+         &kOpticalStabilizationMode, 1);
+  update(ANDROID_LENS_FACING, &kFacing, 1);
+  update(ANDROID_NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
+         &kNoiseReductionMode, 1);
+  update(ANDROID_NOISE_REDUCTION_MODE, &kNoiseReductionMode, 1);
+  update(ANDROID_REQUEST_PARTIAL_RESULT_COUNT, &kPartialResultCount, 1);
+  update(ANDROID_REQUEST_PIPELINE_MAX_DEPTH, &kRequestPipelineMaxDepth, 1);
+  update(ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS, kRequestMaxNumOutputStreams,
+         NELEM(kRequestMaxNumOutputStreams));
+  update(ANDROID_REQUEST_MAX_NUM_INPUT_STREAMS, &kRequestMaxNumInputStreams, 1);
+  update(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM,
+         kScalerAvailableMaxDigitalZoom, NELEM(kScalerAvailableMaxDigitalZoom));
+  update(ANDROID_SCALER_CROPPING_TYPE, &kCroppingType, 1);
+  update(ANDROID_SENSOR_AVAILABLE_TEST_PATTERN_MODES, kTestPatternModes,
+         NELEM(kTestPatternModes));
+  update(ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE, &kTimestampSource, 1);
+  update(ANDROID_SENSOR_ORIENTATION, &kOrientation, 1);
+  update(ANDROID_SHADING_AVAILABLE_MODES, &kAvailableShadingMode, 1);
+  update(ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES, &kFaceDetectMode,
+         1);
+  update(ANDROID_STATISTICS_INFO_MAX_FACE_COUNT, &kMaxFaceCount, 1);
+  update(ANDROID_STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES,
+         &kAvailableHotpixelMode, 1);
+  update(ANDROID_STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES,
+         &kLensShadingMapMode, 1);
+  update(ANDROID_SYNC_MAX_LATENCY, &kMaxLatency, 1);
+  update(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS, kAvailableRequestKeys,
+         NELEM(kAvailableRequestKeys));
+  update(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS, kAvailableResultKeys,
+         NELEM(kAvailableResultKeys));
+  update(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
+         kAvailableCharacteristicsKeys, NELEM(kAvailableCharacteristicsKeys));
+
+  // assume max 2bytes/pixel + info because client might provide us pngs
+  const int32_t jpeg_max_size = width * height * 2 + sizeof(camera3_jpeg_blob);
+  update(ANDROID_JPEG_MAX_SIZE, &jpeg_max_size, 1);
+
+  std::vector<int64_t> min_frame_durations;
+  std::vector<int32_t> stream_configurations;
+  std::vector<int64_t> stall_durations;
+
+  int64_t frame_duration = 1000000000L / fps;
+  for (const auto& format : kHalFormats) {
+    stream_configurations.push_back(format);
+    min_frame_durations.push_back(format);
+    stall_durations.push_back(format);
+    stream_configurations.push_back(width);
+    min_frame_durations.push_back(width);
+    stall_durations.push_back(width);
+    stream_configurations.push_back(height);
+    min_frame_durations.push_back(height);
+    stall_durations.push_back(height);
+    stream_configurations.push_back(
+        ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT);
+    min_frame_durations.push_back(frame_duration);
+    stall_durations.push_back((format == HAL_PIXEL_FORMAT_BLOB) ? 2000000000L
+                                                                : 0);
+  }
+  update(ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
+         stream_configurations.data(), stream_configurations.size());
+  update(ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS,
+         min_frame_durations.data(), min_frame_durations.size());
+  update(ANDROID_SCALER_AVAILABLE_STALL_DURATIONS, stall_durations.data(),
+         stall_durations.size());
+
+  int32_t active_array_size[] = {0, 0, width, height};
+  update(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE,
+         active_array_size, NELEM(active_array_size));
+  update(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE, active_array_size,
+         NELEM(active_array_size));
+
+  int32_t pixel_array_size[] = {width, height};
+  update(ANDROID_SENSOR_INFO_PIXEL_ARRAY_SIZE, pixel_array_size,
+         NELEM(pixel_array_size));
+
+  int32_t max_frame_rate = fps;
+  int32_t min_frame_rate = max_frame_rate / 2;
+  int32_t frame_rates[] = {min_frame_rate, max_frame_rate};
+  update(ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, frame_rates,
+         NELEM(frame_rates));
+  int64_t max_frame_duration = 1000000000L / min_frame_rate;
+  update(ANDROID_SENSOR_INFO_MAX_FRAME_DURATION, &max_frame_duration, 1);
+}
+
+VsockCameraRequestMetadata::VsockCameraRequestMetadata(int32_t fps,
+                                                       RequestTemplate type) {
+  update(ANDROID_COLOR_CORRECTION_ABERRATION_MODE, &kAberrationMode, 1);
+  update(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION, &kExposureCompensation, 1);
+  update(ANDROID_CONTROL_VIDEO_STABILIZATION_MODE, &kVideoStabilizationMode, 1);
+  update(ANDROID_CONTROL_AWB_MODE, &kAwbAvailableMode, 1);
+  update(ANDROID_CONTROL_AE_MODE, &kAeAvailableMode, 1);
+  update(ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER, &kAePrecaptureTrigger, 1);
+  update(ANDROID_CONTROL_AF_MODE, &kAfMode, 1);
+  update(ANDROID_CONTROL_AF_TRIGGER, &kAfTrigger, 1);
+  update(ANDROID_CONTROL_SCENE_MODE, &kAvailableSceneMode, 1);
+  update(ANDROID_CONTROL_EFFECT_MODE, &kAvailableFffect, 1);
+  update(ANDROID_FLASH_MODE, &kFlashMode, 1);
+  update(ANDROID_JPEG_THUMBNAIL_SIZE, kThumbnailSize, NELEM(kThumbnailSize));
+  update(ANDROID_JPEG_QUALITY, &kJpegQuality, 1);
+  update(ANDROID_JPEG_THUMBNAIL_QUALITY, &kJpegQuality, 1);
+  update(ANDROID_JPEG_ORIENTATION, &kJpegOrientation, 1);
+  update(ANDROID_LENS_OPTICAL_STABILIZATION_MODE, &kOpticalStabilizationMode,
+         1);
+  update(ANDROID_NOISE_REDUCTION_MODE, &kNoiseReductionMode, 1);
+  update(ANDROID_SENSOR_TEST_PATTERN_MODE, &kTestPatternMode, 1);
+  update(ANDROID_STATISTICS_FACE_DETECT_MODE, &kFaceDetectMode, 1);
+  update(ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE, &kAvailableHotpixelMode, 1);
+
+  int32_t max_frame_rate = fps;
+  int32_t min_frame_rate = max_frame_rate / 2;
+  int32_t frame_rates[] = {min_frame_rate, max_frame_rate};
+  update(ANDROID_CONTROL_AE_TARGET_FPS_RANGE, frame_rates, NELEM(frame_rates));
+
+  update(ANDROID_CONTROL_AE_ANTIBANDING_MODE, &kAntibandingMode, 1);
+  update(ANDROID_CONTROL_MODE, &kControlMode, 1);
+
+  auto it = kTemplateToIntent.find(type);
+  if (it != kTemplateToIntent.end()) {
+    auto intent = it->second;
+    update(ANDROID_CONTROL_CAPTURE_INTENT, &intent, 1);
+    is_valid_ = true;
+  } else {
+    is_valid_ = false;
+  }
+}
+
+}  // namespace android::hardware::camera::device::V3_4::implementation
diff --git a/guest/hals/camera/vsock_camera_metadata.h b/guest/hals/camera/vsock_camera_metadata.h
new file mode 100644
index 0000000..d5b43c7
--- /dev/null
+++ b/guest/hals/camera/vsock_camera_metadata.h
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <CameraMetadata.h>
+#include <android/hardware/camera/device/3.2/ICameraDevice.h>
+
+namespace android::hardware::camera::device::V3_4::implementation {
+
+// Small wrappers for mostly hard-coded camera metadata
+// Some parameters are calculated from remote camera frame size and fps
+using ::android::hardware::camera::common::V1_0::helper::CameraMetadata;
+class VsockCameraMetadata : public CameraMetadata {
+ public:
+  VsockCameraMetadata(int32_t width, int32_t height, int32_t fps);
+
+  int32_t getPreferredWidth() const { return width_; }
+  int32_t getPreferredHeight() const { return height_; }
+  int32_t getPreferredFps() const { return fps_; }
+
+ private:
+  int32_t width_;
+  int32_t height_;
+  int32_t fps_;
+};
+
+using ::android::hardware::camera::device::V3_2::RequestTemplate;
+class VsockCameraRequestMetadata : public CameraMetadata {
+ public:
+  VsockCameraRequestMetadata(int32_t fps, RequestTemplate type);
+  // Tells whether the metadata has been successfully constructed
+  // from the parameters
+  bool isValid() const { return is_valid_; }
+
+ private:
+  bool is_valid_;
+};
+
+}  // namespace android::hardware::camera::device::V3_4::implementation
diff --git a/guest/hals/camera/vsock_camera_provider_2_6.cpp b/guest/hals/camera/vsock_camera_provider_2_6.cpp
new file mode 100644
index 0000000..e39e1d3
--- /dev/null
+++ b/guest/hals/camera/vsock_camera_provider_2_6.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright 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.
+ */
+#define LOG_TAG "VsockCameraProvider"
+#include "vsock_camera_provider_2_6.h"
+#include <cutils/properties.h>
+#include <log/log.h>
+#include "vsock_camera_server.h"
+
+namespace android::hardware::camera::provider::V2_6::implementation {
+
+namespace {
+VsockCameraServer gCameraServer;
+constexpr auto kDeviceName = "device@3.4/external/0";
+}  // namespace
+
+using android::hardware::camera::provider::V2_6::ICameraProvider;
+extern "C" ICameraProvider* HIDL_FETCH_ICameraProvider(const char* name) {
+  return (strcmp(name, "external/0") == 0)
+             ? new VsockCameraProvider(&gCameraServer)
+             : nullptr;
+}
+
+VsockCameraProvider::VsockCameraProvider(VsockCameraServer* server) {
+  server_ = server;
+  if (!server->isRunning()) {
+    constexpr static const auto camera_port_property =
+        "ro.boot.vsock_camera_port";
+    constexpr static const auto camera_cid_property =
+        "ro.boot.vsock_camera_cid";
+    auto port = property_get_int32(camera_port_property, -1);
+    auto cid = property_get_int32(camera_cid_property, -1);
+    if (port > 0) {
+      server->start(port, cid);
+    }
+  }
+}
+
+VsockCameraProvider::~VsockCameraProvider() {
+  server_->setConnectedCallback(nullptr);
+}
+
+Return<Status> VsockCameraProvider::setCallback(
+    const sp<ICameraProviderCallback>& callback) {
+  {
+    std::lock_guard<std::mutex> lock(mutex_);
+    callbacks_ = callback;
+  }
+  server_->setConnectedCallback(
+      [this](std::shared_ptr<cuttlefish::VsockConnection> connection,
+             VsockCameraDevice::Settings settings) {
+        connection_ = connection;
+        settings_ = settings;
+        deviceAdded(kDeviceName);
+        connection_->SetDisconnectCallback(
+            [this] { deviceRemoved(kDeviceName); });
+      });
+  return Status::OK;
+}
+
+Return<void> VsockCameraProvider::getVendorTags(
+    ICameraProvider::getVendorTags_cb _hidl_cb) {
+  // No vendor tag support
+  hidl_vec<VendorTagSection> empty;
+  _hidl_cb(Status::OK, empty);
+  return Void();
+}
+
+Return<void> VsockCameraProvider::getCameraIdList(
+    ICameraProvider::getCameraIdList_cb _hidl_cb) {
+  // External camera HAL always report 0 camera, and extra cameras
+  // are just reported via cameraDeviceStatusChange callbacks
+  hidl_vec<hidl_string> empty;
+  _hidl_cb(Status::OK, empty);
+  return Void();
+}
+
+Return<void> VsockCameraProvider::isSetTorchModeSupported(
+    ICameraProvider::isSetTorchModeSupported_cb _hidl_cb) {
+  // setTorchMode API is supported, though right now no external camera device
+  // has a flash unit.
+  _hidl_cb(Status::OK, true);
+  return Void();
+}
+
+Return<void> VsockCameraProvider::getCameraDeviceInterface_V1_x(
+    const hidl_string&,
+    ICameraProvider::getCameraDeviceInterface_V1_x_cb _hidl_cb) {
+  // External Camera HAL does not support HAL1
+  _hidl_cb(Status::OPERATION_NOT_SUPPORTED, nullptr);
+  return Void();
+}
+
+Return<void> VsockCameraProvider::getCameraDeviceInterface_V3_x(
+    const hidl_string& name_hidl_str,
+    ICameraProvider::getCameraDeviceInterface_V3_x_cb _hidl_cb) {
+  std::string name(name_hidl_str.c_str());
+  if (name != kDeviceName) {
+    _hidl_cb(Status::ILLEGAL_ARGUMENT, nullptr);
+    return Void();
+  }
+
+  _hidl_cb(Status::OK, new VsockCameraDevice(name, settings_, connection_));
+  return Void();
+}
+
+Return<void> VsockCameraProvider::notifyDeviceStateChange(
+    hardware::hidl_bitfield<DeviceState> /*newState*/) {
+  return Void();
+}
+
+Return<void> VsockCameraProvider::getConcurrentStreamingCameraIds(
+    getConcurrentStreamingCameraIds_cb _hidl_cb) {
+  hidl_vec<hidl_vec<hidl_string>> hidl_camera_id_combinations;
+  _hidl_cb(Status::OK, hidl_camera_id_combinations);
+  return Void();
+}
+
+Return<void> VsockCameraProvider::isConcurrentStreamCombinationSupported(
+    const hidl_vec<CameraIdAndStreamCombination>&,
+    isConcurrentStreamCombinationSupported_cb _hidl_cb) {
+  _hidl_cb(Status::OK, false);
+  return Void();
+}
+
+void VsockCameraProvider::deviceAdded(const char* name) {
+  std::lock_guard<std::mutex> lock(mutex_);
+  if (callbacks_ != nullptr) {
+    callbacks_->cameraDeviceStatusChange(name, CameraDeviceStatus::PRESENT);
+  }
+}
+
+void VsockCameraProvider::deviceRemoved(const char* name) {
+  std::lock_guard<std::mutex> lock(mutex_);
+  if (callbacks_ != nullptr) {
+    callbacks_->cameraDeviceStatusChange(name, CameraDeviceStatus::NOT_PRESENT);
+  }
+}
+
+}  // namespace android::hardware::camera::provider::V2_6::implementation
diff --git a/guest/hals/camera/vsock_camera_provider_2_6.h b/guest/hals/camera/vsock_camera_provider_2_6.h
new file mode 100644
index 0000000..67b64f5
--- /dev/null
+++ b/guest/hals/camera/vsock_camera_provider_2_6.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <mutex>
+
+#include <android/hardware/camera/provider/2.6/ICameraProvider.h>
+#include <hidl/MQDescriptor.h>
+#include <hidl/Status.h>
+#include <json/json.h>
+
+#include "vsock_camera_device_3_4.h"
+#include "vsock_camera_server.h"
+#include "vsock_connection.h"
+
+namespace android::hardware::camera::provider::V2_6::implementation {
+
+using ::android::sp;
+using ::android::hardware::hidl_string;
+using ::android::hardware::Return;
+using ::android::hardware::camera::common::V1_0::CameraDeviceStatus;
+using ::android::hardware::camera::common::V1_0::Status;
+using ::android::hardware::camera::common::V1_0::VendorTagSection;
+using ::android::hardware::camera::device::V3_4::implementation::
+    VsockCameraDevice;
+using ::android::hardware::camera::provider::V2_4::ICameraProviderCallback;
+using ::android::hardware::camera::provider::V2_5::DeviceState;
+using ::android::hardware::camera::provider::V2_6::CameraIdAndStreamCombination;
+using ::android::hardware::camera::provider::V2_6::ICameraProvider;
+
+class VsockCameraProvider : public ICameraProvider {
+ public:
+  VsockCameraProvider(VsockCameraServer* server);
+  ~VsockCameraProvider();
+
+  Return<Status> setCallback(
+      const sp<ICameraProviderCallback>& callback) override;
+  Return<void> getVendorTags(getVendorTags_cb _hidl_cb) override;
+  Return<void> getCameraIdList(getCameraIdList_cb _hidl_cb) override;
+  Return<void> isSetTorchModeSupported(
+      isSetTorchModeSupported_cb _hidl_cb) override;
+  Return<void> getCameraDeviceInterface_V1_x(
+      const hidl_string& cameraDeviceName,
+      getCameraDeviceInterface_V1_x_cb _hidl_cb) override;
+  Return<void> getCameraDeviceInterface_V3_x(
+      const hidl_string& cameraDeviceName,
+      getCameraDeviceInterface_V3_x_cb _hidl_cb) override;
+  Return<void> notifyDeviceStateChange(
+      hardware::hidl_bitfield<DeviceState> newState) override;
+  Return<void> getConcurrentStreamingCameraIds(
+      getConcurrentStreamingCameraIds_cb _hidl_cb) override;
+  Return<void> isConcurrentStreamCombinationSupported(
+      const hidl_vec<CameraIdAndStreamCombination>& configs,
+      isConcurrentStreamCombinationSupported_cb _hidl_cb) override;
+
+ private:
+  void deviceRemoved(const char* name);
+  void deviceAdded(const char* name);
+  std::mutex mutex_;
+  sp<ICameraProviderCallback> callbacks_;
+  std::shared_ptr<cuttlefish::VsockConnection> connection_;
+  VsockCameraDevice::Settings settings_;
+  VsockCameraServer* server_;
+};
+
+}  // namespace android::hardware::camera::provider::V2_6::implementation
diff --git a/guest/hals/camera/vsock_camera_server.cpp b/guest/hals/camera/vsock_camera_server.cpp
new file mode 100644
index 0000000..381a985
--- /dev/null
+++ b/guest/hals/camera/vsock_camera_server.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "VsockCameraServer"
+#include "vsock_camera_server.h"
+#include <log/log.h>
+
+namespace android::hardware::camera::provider::V2_6::implementation {
+
+using ::android::hardware::camera::device::V3_4::implementation::
+    VsockCameraDevice;
+namespace {
+
+bool containsValidSettings(const VsockCameraDevice::Settings& settings) {
+  return settings.width > 0 && settings.height > 0 && settings.frame_rate > 0.0;
+}
+
+bool readSettingsFromJson(VsockCameraDevice::Settings& settings,
+                          const Json::Value& json) {
+  VsockCameraDevice::Settings new_settings;
+  new_settings.width = json["width"].asInt();
+  new_settings.height = json["height"].asInt();
+  new_settings.frame_rate = json["frame_rate"].asDouble();
+  if (containsValidSettings(new_settings)) {
+    settings = new_settings;
+    return true;
+  } else {
+    return false;
+  }
+}
+
+}  // namespace
+
+VsockCameraServer::VsockCameraServer() {
+  ALOGI("%s: Create server", __FUNCTION__);
+  connection_ = std::make_shared<cuttlefish::VsockServerConnection>();
+}
+
+VsockCameraServer::~VsockCameraServer() {
+  ALOGI("%s: Destroy server", __FUNCTION__);
+  stop();
+}
+
+void VsockCameraServer::start(unsigned int port, unsigned int cid) {
+  stop();
+  is_running_ = true;
+  server_thread_ = std::thread([this, port, cid] { serverLoop(port, cid); });
+}
+
+void VsockCameraServer::stop() {
+  connection_->ServerShutdown();
+  is_running_ = false;
+  if (server_thread_.joinable()) {
+    server_thread_.join();
+  }
+}
+
+void VsockCameraServer::setConnectedCallback(callback_t callback) {
+  connected_callback_ = callback;
+  std::lock_guard<std::mutex> lock(settings_mutex_);
+  if (callback && connection_->IsConnected() &&
+      containsValidSettings(settings_)) {
+    callback(connection_, settings_);
+  }
+}
+
+void VsockCameraServer::serverLoop(unsigned int port, unsigned int cid) {
+  while (is_running_.load()) {
+    ALOGI("%s: Accepting connections...", __FUNCTION__);
+    if (connection_->Connect(port, cid)) {
+      auto json_settings = connection_->ReadJsonMessage();
+      VsockCameraDevice::Settings settings;
+      if (readSettingsFromJson(settings, json_settings)) {
+        std::lock_guard<std::mutex> lock(settings_mutex_);
+        settings_ = settings;
+        if (connected_callback_) {
+          connected_callback_(connection_, settings);
+        }
+        ALOGI("%s: Client connected", __FUNCTION__);
+      } else {
+        ALOGE("%s: Could not read settings", __FUNCTION__);
+      }
+    } else {
+      ALOGE("%s: Accepting connections failed", __FUNCTION__);
+    }
+  }
+  ALOGI("%s: Exiting", __FUNCTION__);
+}
+
+}  // namespace android::hardware::camera::provider::V2_6::implementation
diff --git a/guest/hals/camera/vsock_camera_server.h b/guest/hals/camera/vsock_camera_server.h
new file mode 100644
index 0000000..5cc3b38
--- /dev/null
+++ b/guest/hals/camera/vsock_camera_server.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+#pragma once
+#include <atomic>
+#include <thread>
+#include "vsock_camera_device_3_4.h"
+#include "vsock_connection.h"
+
+namespace android::hardware::camera::provider::V2_6::implementation {
+
+using ::android::hardware::camera::device::V3_4::implementation::
+    VsockCameraDevice;
+
+class VsockCameraServer {
+ public:
+  VsockCameraServer();
+  ~VsockCameraServer();
+
+  VsockCameraServer(const VsockCameraServer&) = delete;
+  VsockCameraServer& operator=(const VsockCameraServer&) = delete;
+
+  bool isRunning() const { return is_running_.load(); }
+
+  void start(unsigned int port, unsigned int cid);
+  void stop();
+
+  using callback_t =
+      std::function<void(std::shared_ptr<cuttlefish::VsockConnection>,
+                         VsockCameraDevice::Settings)>;
+  void setConnectedCallback(callback_t callback);
+
+ private:
+  void serverLoop(unsigned int port, unsigned int cid);
+  std::thread server_thread_;
+  std::atomic<bool> is_running_;
+  std::shared_ptr<cuttlefish::VsockServerConnection> connection_;
+  std::mutex settings_mutex_;
+  VsockCameraDevice::Settings settings_;
+  callback_t connected_callback_;
+};
+
+}  // namespace android::hardware::camera::provider::V2_6::implementation
diff --git a/guest/hals/camera/vsock_frame_provider.cpp b/guest/hals/camera/vsock_frame_provider.cpp
new file mode 100644
index 0000000..435d5f9
--- /dev/null
+++ b/guest/hals/camera/vsock_frame_provider.cpp
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+#include "vsock_frame_provider.h"
+#include <hardware/camera3.h>
+#include <libyuv.h>
+#include <cstring>
+#define LOG_TAG "VsockFrameProvider"
+#include <log/log.h>
+
+namespace cuttlefish {
+
+VsockFrameProvider::~VsockFrameProvider() { stop(); }
+
+void VsockFrameProvider::start(
+    std::shared_ptr<cuttlefish::VsockConnection> connection, uint32_t width,
+    uint32_t height) {
+  stop();
+  running_ = true;
+  connection_ = connection;
+  reader_thread_ =
+      std::thread([this, width, height] { VsockReadLoop(width, height); });
+}
+
+void VsockFrameProvider::stop() {
+  running_ = false;
+  jpeg_pending_ = false;
+  if (reader_thread_.joinable()) {
+    reader_thread_.join();
+  }
+  connection_ = nullptr;
+}
+
+bool VsockFrameProvider::waitYUVFrame(unsigned int max_wait_ms) {
+  auto timeout = std::chrono::milliseconds(max_wait_ms);
+  nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+  std::unique_lock<std::mutex> lock(frame_mutex_);
+  return yuv_frame_updated_.wait_for(
+      lock, timeout, [this, now] { return timestamp_.load() > now; });
+}
+
+void VsockFrameProvider::requestJpeg() {
+  jpeg_pending_ = true;
+  Json::Value message;
+  message["event"] = "VIRTUAL_DEVICE_CAPTURE_IMAGE";
+  if (connection_) {
+    connection_->WriteMessage(message);
+  }
+}
+
+void VsockFrameProvider::cancelJpegRequest() { jpeg_pending_ = false; }
+
+bool VsockFrameProvider::copyYUVFrame(uint32_t w, uint32_t h, YCbCrLayout dst) {
+  size_t y_size = w * h;
+  size_t cbcr_size = (w / 2) * (h / 2);
+  size_t total_size = y_size + 2 * cbcr_size;
+  std::lock_guard<std::mutex> lock(frame_mutex_);
+  if (frame_.size() < total_size) {
+    ALOGE("%s: %zu is too little for %ux%u frame", __FUNCTION__, frame_.size(),
+          w, h);
+    return false;
+  }
+  if (dst.y == nullptr) {
+    ALOGE("%s: Destination is nullptr!", __FUNCTION__);
+    return false;
+  }
+  YCbCrLayout src{.y = static_cast<void*>(frame_.data()),
+                  .cb = static_cast<void*>(frame_.data() + y_size),
+                  .cr = static_cast<void*>(frame_.data() + y_size + cbcr_size),
+                  .yStride = w,
+                  .cStride = w / 2,
+                  .chromaStep = 1};
+  uint8_t* src_y = static_cast<uint8_t*>(src.y);
+  uint8_t* dst_y = static_cast<uint8_t*>(dst.y);
+  uint8_t* src_cb = static_cast<uint8_t*>(src.cb);
+  uint8_t* dst_cb = static_cast<uint8_t*>(dst.cb);
+  uint8_t* src_cr = static_cast<uint8_t*>(src.cr);
+  uint8_t* dst_cr = static_cast<uint8_t*>(dst.cr);
+  libyuv::CopyPlane(src_y, src.yStride, dst_y, dst.yStride, w, h);
+  if (dst.chromaStep == 1) {
+    // Planar
+    libyuv::CopyPlane(src_cb, src.cStride, dst_cb, dst.cStride, w / 2, h / 2);
+    libyuv::CopyPlane(src_cr, src.cStride, dst_cr, dst.cStride, w / 2, h / 2);
+  } else if (dst.chromaStep == 2 && dst_cr - dst_cb == 1) {
+    // Interleaved cb/cr planes starting with cb
+    libyuv::MergeUVPlane(src_cb, src.cStride, src_cr, src.cStride, dst_cb,
+                         dst.cStride, w / 2, h / 2);
+  } else if (dst.chromaStep == 2 && dst_cb - dst_cr == 1) {
+    // Interleaved cb/cr planes starting with cr
+    libyuv::MergeUVPlane(src_cr, src.cStride, src_cb, src.cStride, dst_cr,
+                         dst.cStride, w / 2, h / 2);
+  } else {
+    ALOGE("%s: Unsupported interleaved U/V layout", __FUNCTION__);
+    return false;
+  }
+  return true;
+}
+
+bool VsockFrameProvider::copyJpegData(uint32_t size, void* dst) {
+  std::lock_guard<std::mutex> lock(jpeg_mutex_);
+  auto jpeg_header_offset = size - sizeof(struct camera3_jpeg_blob);
+  if (cached_jpeg_.empty()) {
+    ALOGE("%s: No source data", __FUNCTION__);
+    return false;
+  } else if (dst == nullptr) {
+    ALOGE("%s: Destination is nullptr", __FUNCTION__);
+    return false;
+  } else if (jpeg_header_offset <= cached_jpeg_.size()) {
+    ALOGE("%s: %ubyte target buffer too small", __FUNCTION__, size);
+    return false;
+  }
+  std::memcpy(dst, cached_jpeg_.data(), cached_jpeg_.size());
+  struct camera3_jpeg_blob* blob = reinterpret_cast<struct camera3_jpeg_blob*>(
+      static_cast<char*>(dst) + jpeg_header_offset);
+  blob->jpeg_blob_id = CAMERA3_JPEG_BLOB_ID;
+  blob->jpeg_size = cached_jpeg_.size();
+  cached_jpeg_.clear();
+  return true;
+}
+
+bool VsockFrameProvider::isBlob(const std::vector<char>& blob) {
+  bool is_png = blob.size() > 4 && (blob[0] & 0xff) == 0x89 &&
+                (blob[1] & 0xff) == 0x50 && (blob[2] & 0xff) == 0x4e &&
+                (blob[3] & 0xff) == 0x47;
+  bool is_jpeg =
+      blob.size() > 2 && (blob[0] & 0xff) == 0xff && (blob[1] & 0xff) == 0xd8;
+  return is_png || is_jpeg;
+}
+
+bool VsockFrameProvider::framesizeMatches(uint32_t width, uint32_t height,
+                                          const std::vector<char>& data) {
+  return data.size() == 3 * width * height / 2;
+}
+
+void VsockFrameProvider::VsockReadLoop(uint32_t width, uint32_t height) {
+  jpeg_pending_ = false;
+  while (running_.load() && connection_->ReadMessage(next_frame_)) {
+    if (framesizeMatches(width, height, next_frame_)) {
+      std::lock_guard<std::mutex> lock(frame_mutex_);
+      timestamp_ = systemTime();
+      frame_.swap(next_frame_);
+      yuv_frame_updated_.notify_one();
+    } else if (isBlob(next_frame_)) {
+      std::lock_guard<std::mutex> lock(jpeg_mutex_);
+      bool was_pending = jpeg_pending_.exchange(false);
+      if (was_pending) {
+        cached_jpeg_.swap(next_frame_);
+      }
+    } else {
+      ALOGE("%s: Unexpected data of %zu bytes", __FUNCTION__,
+            next_frame_.size());
+    }
+  }
+  if (!connection_->IsConnected()) {
+    ALOGE("%s: Connection closed - exiting", __FUNCTION__);
+    running_ = false;
+  }
+}
+
+}  // namespace cuttlefish
diff --git a/guest/hals/camera/vsock_frame_provider.h b/guest/hals/camera/vsock_frame_provider.h
new file mode 100644
index 0000000..4454d3e
--- /dev/null
+++ b/guest/hals/camera/vsock_frame_provider.h
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+#pragma once
+#include <android/hardware/graphics/mapper/2.0/IMapper.h>
+#include <atomic>
+#include <mutex>
+#include <thread>
+#include <vector>
+#include "utils/Timers.h"
+#include "vsock_connection.h"
+
+namespace cuttlefish {
+
+using ::android::hardware::graphics::mapper::V2_0::YCbCrLayout;
+
+// VsockFrameProvider reads data from vsock
+// Users can get the data by using copyYUVFrame/copyJpegData methods
+class VsockFrameProvider {
+ public:
+  VsockFrameProvider() = default;
+  ~VsockFrameProvider();
+
+  VsockFrameProvider(const VsockFrameProvider&) = delete;
+  VsockFrameProvider& operator=(const VsockFrameProvider&) = delete;
+
+  void start(std::shared_ptr<cuttlefish::VsockConnection> connection,
+             uint32_t expected_width, uint32_t expected_height);
+  void stop();
+  void requestJpeg();
+  void cancelJpegRequest();
+  bool jpegPending() const { return jpeg_pending_.load(); }
+  bool isRunning() const { return running_.load(); }
+  bool waitYUVFrame(unsigned int max_wait_ms);
+  bool copyYUVFrame(uint32_t width, uint32_t height, YCbCrLayout dst);
+  bool copyJpegData(uint32_t size, void* dst);
+
+ private:
+  bool isBlob(const std::vector<char>& blob);
+  bool framesizeMatches(uint32_t width, uint32_t height,
+                        const std::vector<char>& data);
+  void VsockReadLoop(uint32_t expected_width, uint32_t expected_height);
+  std::thread reader_thread_;
+  std::mutex frame_mutex_;
+  std::mutex jpeg_mutex_;
+  std::atomic<nsecs_t> timestamp_;
+  std::atomic<bool> running_;
+  std::atomic<bool> jpeg_pending_;
+  std::vector<char> frame_;
+  std::vector<char> next_frame_;
+  std::vector<char> cached_jpeg_;
+  std::condition_variable yuv_frame_updated_;
+  std::shared_ptr<cuttlefish::VsockConnection> connection_;
+};
+
+}  // namespace cuttlefish
diff --git a/guest/hals/confirmationui/.clang-format b/guest/hals/confirmationui/.clang-format
new file mode 100644
index 0000000..b0dc94c
--- /dev/null
+++ b/guest/hals/confirmationui/.clang-format
@@ -0,0 +1,10 @@
+BasedOnStyle: LLVM
+IndentWidth: 4
+UseTab: Never
+BreakBeforeBraces: Attach
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: true
+IndentCaseLabels: false
+ColumnLimit: 100
+PointerBindsToType: true
+SpacesBeforeTrailingComments: 2
diff --git a/guest/hals/confirmationui/Android.bp b/guest/hals/confirmationui/Android.bp
new file mode 100644
index 0000000..8ee78a0
--- /dev/null
+++ b/guest/hals/confirmationui/Android.bp
@@ -0,0 +1,86 @@
+// 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.
+//
+
+// WARNING: Everything listed here will be built on ALL platforms,
+// including x86, the emulator, and the SDK.  Modules must be uniquely
+// named (liblights.panda), and must build everywhere, or limit themselves
+// to only building on ARM if they include assembly. Individual makefiles
+// are responsible for having their own logic, for fine-grained control.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "android.hardware.confirmationui@1.0-service.cuttlefish",
+    defaults: ["hidl_defaults", "cuttlefish_guest_only"],
+    relative_install_path: "hw",
+    vendor: true,
+    shared_libs: [
+        "android.hardware.confirmationui@1.0",
+        "android.hardware.confirmationui.not-so-secure-input",
+        "android.hardware.confirmationui@1.0-lib.cuttlefish",
+        "libbase",
+        "libhidlbase",
+        "libutils",
+    ],
+
+    init_rc: ["android.hardware.confirmationui@1.0-service.cuttlefish.rc"],
+
+    vintf_fragments: ["android.hardware.confirmationui@1.0-service.cuttlefish.xml"],
+
+    srcs: [
+        "service.cpp",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-DTEEUI_USE_STD_VECTOR",
+    ],
+}
+
+cc_library {
+    name: "android.hardware.confirmationui@1.0-lib.cuttlefish",
+    defaults: ["hidl_defaults", "cuttlefish_guest_only"],
+    vendor: true,
+    shared_libs: [
+        "android.hardware.confirmationui@1.0",
+        "android.hardware.keymaster@4.0",
+        "libbase",
+        "libdmabufheap",
+        "libhidlbase",
+        "libteeui_hal_support",
+        "libtrusty",
+        "libutils",
+    ],
+
+    export_include_dirs: ["include"],
+
+    srcs: [
+        "TrustyApp.cpp",
+        "TrustyConfirmationUI.cpp",
+    ],
+    static_libs: [
+        "libcuttlefish_fs",
+        "libcuttlefish_confui",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-DTEEUI_USE_STD_VECTOR",
+    ],
+}
+
diff --git a/guest/hals/confirmationui/README b/guest/hals/confirmationui/README
new file mode 100644
index 0000000..45d4e76
--- /dev/null
+++ b/guest/hals/confirmationui/README
@@ -0,0 +1,20 @@
+## Secure UI Architecture
+
+To implement confirmationui a secure UI architecture is required. This entails a way
+to display the confirmation dialog driven by a reduced trusted computing base, typically
+a trusted execution environment (TEE), without having to rely on Linux and the Android
+system for integrity and authenticity of input events. This implementation provides
+neither. But it provides most of the functionlity required to run a full Android Protected
+Confirmation feature when integrated into a secure UI architecture.
+
+## Secure input (NotSoSecureInput)
+
+This implementation does not provide any security guaranties.
+The input method (NotSoSecureInput) runs a cryptographic protocols that is
+sufficiently secure IFF the end point is implemented on a trustworthy
+secure input device. But since the endpoint is currently in the HAL
+service itself this implementation is not secure.
+
+NOTE that a secure input device end point needs a good source of entropy
+for generating nonces. The current implementation (NotSoSecureInput.cpp#generateNonce)
+uses a constant nonce.
\ No newline at end of file
diff --git a/guest/hals/confirmationui/TrustyApp.cpp b/guest/hals/confirmationui/TrustyApp.cpp
new file mode 100644
index 0000000..cee8655
--- /dev/null
+++ b/guest/hals/confirmationui/TrustyApp.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright 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.
+ */
+
+#include "TrustyApp.h"
+
+#include <BufferAllocator/BufferAllocator.h>
+#include <android-base/logging.h>
+#include <sys/mman.h>
+#include <sys/uio.h>
+#include <trusty/tipc.h>
+
+#define countof(arr) (sizeof(arr) / sizeof(arr[0]))
+
+namespace android {
+namespace trusty {
+namespace confirmationui {
+
+using ::android::base::unique_fd;
+
+static inline uintptr_t RoundPageUp(uintptr_t val) {
+    return (val + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
+}
+
+ssize_t TrustyApp::TrustyRpc(const uint8_t* obegin, const uint8_t* oend, uint8_t* ibegin,
+                             uint8_t* iend) {
+    uint32_t olen = oend - obegin;
+
+    if (olen > shm_len_) {
+        LOG(ERROR) << AT << "request message too long to fit in shared memory";
+        return -1;
+    }
+
+    memcpy(shm_base_, obegin, olen);
+
+    confirmationui_hdr hdr = {
+        .cmd = CONFIRMATIONUI_CMD_MSG,
+    };
+    confirmationui_msg_args args = {
+        .msg_len = olen,
+    };
+    iovec iov[] = {
+        {
+            .iov_base = &hdr,
+            .iov_len = sizeof(hdr),
+        },
+        {
+            .iov_base = &args,
+            .iov_len = sizeof(args),
+        },
+    };
+
+    int rc = tipc_send(handle_, iov, countof(iov), NULL, 0);
+    if (rc != static_cast<int>(sizeof(hdr) + sizeof(args))) {
+        LOG(ERROR) << AT << "failed to send MSG request";
+        return -1;
+    }
+
+    rc = readv(handle_, iov, countof(iov));
+    if (rc != static_cast<int>(sizeof(hdr) + sizeof(args))) {
+        LOG(ERROR) << AT << "failed to receive MSG response";
+        return -1;
+    }
+
+    if (hdr.cmd != (CONFIRMATIONUI_CMD_MSG | CONFIRMATIONUI_RESP_BIT)) {
+        LOG(ERROR) << AT << "unknown response command: " << hdr.cmd;
+        return -1;
+    }
+
+    uint32_t ilen = iend - ibegin;
+    if (args.msg_len > ilen) {
+        LOG(ERROR) << AT << "response message too long to fit in return buffer";
+        return -1;
+    }
+
+    memcpy(ibegin, shm_base_, args.msg_len);
+
+    return args.msg_len;
+}
+
+TrustyApp::TrustyApp(const std::string& path, const std::string& appname)
+    : handle_(kInvalidHandle) {
+    unique_fd tipc_handle(tipc_connect(path.c_str(), appname.c_str()));
+    if (tipc_handle < 0) {
+        LOG(ERROR) << AT << "failed to connect to Trusty TA \"" << appname << "\" using dev:"
+                   << "\"" << path << "\"";
+        return;
+    }
+
+    uint32_t shm_len = RoundPageUp(CONFIRMATIONUI_MAX_MSG_SIZE);
+    BufferAllocator allocator;
+    unique_fd dma_buf(allocator.Alloc("system", shm_len));
+    if (dma_buf < 0) {
+        LOG(ERROR) << AT << "failed to allocate shared memory buffer";
+        return;
+    }
+
+    confirmationui_hdr hdr = {
+        .cmd = CONFIRMATIONUI_CMD_INIT,
+    };
+    confirmationui_init_req args = {
+        .shm_len = shm_len,
+    };
+    iovec iov[] = {
+        {
+            .iov_base = &hdr,
+            .iov_len = sizeof(hdr),
+        },
+        {
+            .iov_base = &args,
+            .iov_len = sizeof(args),
+        },
+    };
+    trusty_shm shm = {
+        .fd = dma_buf,
+        .transfer = TRUSTY_SHARE,
+    };
+
+    int rc = tipc_send(tipc_handle, iov, 2, &shm, 1);
+    if (rc != static_cast<int>(sizeof(hdr) + sizeof(args))) {
+        LOG(ERROR) << AT << "failed to send INIT request";
+        return;
+    }
+
+    rc = read(tipc_handle, &hdr, sizeof(hdr));
+    if (rc != static_cast<int>(sizeof(hdr))) {
+        LOG(ERROR) << AT << "failed to receive INIT response";
+        return;
+    }
+
+    if (hdr.cmd != (CONFIRMATIONUI_CMD_INIT | CONFIRMATIONUI_RESP_BIT)) {
+        LOG(ERROR) << AT << "unknown response command: " << hdr.cmd;
+        return;
+    }
+
+    void* shm_base = mmap(0, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, dma_buf, 0);
+    if (shm_base == MAP_FAILED) {
+        LOG(ERROR) << AT << "failed to mmap() shared memory buffer";
+        return;
+    }
+
+    handle_ = std::move(tipc_handle);
+    shm_base_ = shm_base;
+    shm_len_ = shm_len;
+
+    LOG(INFO) << AT << "succeeded to connect to Trusty TA \"" << appname << "\"";
+}
+
+TrustyApp::~TrustyApp() {
+    LOG(INFO) << "Done shutting down TrustyApp";
+}
+
+}  // namespace confirmationui
+}  // namespace trusty
+}  // namespace android
diff --git a/guest/hals/confirmationui/TrustyApp.h b/guest/hals/confirmationui/TrustyApp.h
new file mode 100644
index 0000000..fb57828
--- /dev/null
+++ b/guest/hals/confirmationui/TrustyApp.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright 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 <TrustyIpc.h>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdio.h>
+#include <sys/eventfd.h>
+#include <sys/stat.h>
+#include <teeui/msg_formatting.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <functional>
+#include <future>
+#include <iostream>
+#include <sstream>
+#include <thread>
+#include <vector>
+
+#define AT __FILE__ ":" << __LINE__ << ": "
+
+namespace android {
+namespace trusty {
+namespace confirmationui {
+
+using ::teeui::Message;
+using ::teeui::msg2tuple_t;
+using ::teeui::ReadStream;
+using ::teeui::WriteStream;
+
+#ifndef TEEUI_USE_STD_VECTOR
+/*
+ * TEEUI_USE_STD_VECTOR makes certain wire types like teeui::MsgString and
+ * teeui::MsgVector be aliases for std::vector. This is required for thread safe
+ * message serialization. Always compile this with -DTEEUI_USE_STD_VECTOR set in
+ * CFLAGS of the HAL service.
+ */
+#error "Must be compiled with -DTEEUI_USE_STD_VECTOR."
+#endif
+
+enum class TrustyAppError : int32_t {
+    OK,
+    ERROR = -1,
+    MSG_TOO_LONG = -2,
+};
+
+class TrustyApp {
+  private:
+    android::base::unique_fd handle_;
+    void* shm_base_;
+    size_t shm_len_;
+    static constexpr const int kInvalidHandle = -1;
+    /*
+     * This mutex serializes communication with the trusted app, not handle_.
+     * Calling issueCmd during construction or deletion is undefined behavior.
+     */
+    std::mutex mutex_;
+
+  public:
+    TrustyApp(const std::string& path, const std::string& appname);
+    ~TrustyApp();
+
+    ssize_t TrustyRpc(const uint8_t* obegin, const uint8_t* oend, uint8_t* ibegin, uint8_t* iend);
+
+    template <typename Request, typename Response, typename... T>
+    std::tuple<TrustyAppError, msg2tuple_t<Response>> issueCmd(const T&... args) {
+        std::lock_guard<std::mutex> lock(mutex_);
+
+        if (handle_ == kInvalidHandle) {
+            LOG(ERROR) << "TrustyApp not connected";
+            return {TrustyAppError::ERROR, {}};
+        }
+
+        uint8_t buffer[CONFIRMATIONUI_MAX_MSG_SIZE];
+        WriteStream out(buffer);
+
+        out = write(Request(), out, args...);
+        if (!out) {
+            LOG(ERROR) << AT << "send command failed: message formatting";
+            return {TrustyAppError::MSG_TOO_LONG, {}};
+        }
+
+        auto rc = TrustyRpc(&buffer[0], const_cast<const uint8_t*>(out.pos()), &buffer[0],
+                            &buffer[CONFIRMATIONUI_MAX_MSG_SIZE]);
+        if (rc < 0) return {TrustyAppError::ERROR, {}};
+
+        ReadStream in(&buffer[0], rc);
+        auto result = read(Response(), in);
+        if (!std::get<0>(result)) {
+            LOG(ERROR) << "send command failed: message parsing";
+            return {TrustyAppError::ERROR, {}};
+        }
+
+        return {std::get<0>(result) ? TrustyAppError::OK : TrustyAppError::ERROR,
+                tuple_tail(std::move(result))};
+    }
+
+    template <typename Request, typename... T> TrustyAppError issueCmd(const T&... args) {
+        std::lock_guard<std::mutex> lock(mutex_);
+
+        if (handle_ == kInvalidHandle) {
+            LOG(ERROR) << "TrustyApp not connected";
+            return TrustyAppError::ERROR;
+        }
+
+        uint8_t buffer[CONFIRMATIONUI_MAX_MSG_SIZE];
+        WriteStream out(buffer);
+
+        out = write(Request(), out, args...);
+        if (!out) {
+            LOG(ERROR) << AT << "send command failed: message formatting";
+            return TrustyAppError::MSG_TOO_LONG;
+        }
+
+        auto rc = TrustyRpc(&buffer[0], const_cast<const uint8_t*>(out.pos()), &buffer[0],
+                            &buffer[CONFIRMATIONUI_MAX_MSG_SIZE]);
+        if (rc < 0) {
+            LOG(ERROR) << "send command failed: " << strerror(errno) << " (" << errno << ")";
+            return TrustyAppError::ERROR;
+        }
+
+        if (rc > 0) {
+            LOG(ERROR) << "Unexpected non zero length response";
+            return TrustyAppError::ERROR;
+        }
+        return TrustyAppError::OK;
+    }
+
+    operator bool() const { return handle_ != kInvalidHandle; }
+};
+
+}  // namespace confirmationui
+}  // namespace trusty
+}  // namespace android
diff --git a/guest/hals/confirmationui/TrustyConfirmationUI.cpp b/guest/hals/confirmationui/TrustyConfirmationUI.cpp
new file mode 100644
index 0000000..5112438
--- /dev/null
+++ b/guest/hals/confirmationui/TrustyConfirmationUI.cpp
@@ -0,0 +1,513 @@
+/*
+ *
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TrustyConfirmationUI.h"
+
+#include <android-base/logging.h>
+#include <android/hardware/confirmationui/1.0/types.h>
+#include <android/hardware/keymaster/4.0/types.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <poll.h>
+#include <pthread.h>
+#include <secure_input/evdev.h>
+#include <secure_input/secure_input_device.h>
+#include <secure_input/secure_input_proto.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <teeui/msg_formatting.h>
+#include <teeui/utils.h>
+#include <time.h>
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <thread>
+#include <tuple>
+#include <vector>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+
+using namespace secure_input;
+
+using ::android::trusty::confirmationui::TrustyAppError;
+
+using ::teeui::AbortMsg;
+using ::teeui::DeliverTestCommandMessage;
+using ::teeui::DeliverTestCommandResponse;
+using ::teeui::FetchConfirmationResult;
+using ::teeui::MsgString;
+using ::teeui::MsgVector;
+using ::teeui::PromptUserConfirmationMsg;
+using ::teeui::PromptUserConfirmationResponse;
+using ::teeui::ResultMsg;
+
+using ::secure_input::createSecureInput;
+
+using ::android::hardware::keymaster::V4_0::HardwareAuthToken;
+
+using ::std::tie;
+
+using TeeuiRc = ::teeui::ResponseCode;
+
+constexpr const char kTrustyDeviceName[] = "/dev/trusty-ipc-dev0";
+constexpr const char kConfirmationuiAppName[] = CONFIRMATIONUI_PORT;
+
+namespace {
+
+class Finalize {
+  private:
+    std::function<void()> f_;
+
+  public:
+    Finalize(std::function<void()> f) : f_(f) {}
+    ~Finalize() {
+        if (f_) f_();
+    }
+    void release() { f_ = {}; }
+};
+
+ResponseCode convertRc(TeeuiRc trc) {
+    static_assert(
+        uint32_t(TeeuiRc::OK) == uint32_t(ResponseCode::OK) &&
+            uint32_t(TeeuiRc::Canceled) == uint32_t(ResponseCode::Canceled) &&
+            uint32_t(TeeuiRc::Aborted) == uint32_t(ResponseCode::Aborted) &&
+            uint32_t(TeeuiRc::OperationPending) == uint32_t(ResponseCode::OperationPending) &&
+            uint32_t(TeeuiRc::Ignored) == uint32_t(ResponseCode::Ignored) &&
+            uint32_t(TeeuiRc::SystemError) == uint32_t(ResponseCode::SystemError) &&
+            uint32_t(TeeuiRc::Unimplemented) == uint32_t(ResponseCode::Unimplemented) &&
+            uint32_t(TeeuiRc::Unexpected) == uint32_t(ResponseCode::Unexpected) &&
+            uint32_t(TeeuiRc::UIError) == uint32_t(ResponseCode::UIError) &&
+            uint32_t(TeeuiRc::UIErrorMissingGlyph) == uint32_t(ResponseCode::UIErrorMissingGlyph) &&
+            uint32_t(TeeuiRc::UIErrorMessageTooLong) ==
+                uint32_t(ResponseCode::UIErrorMessageTooLong) &&
+            uint32_t(TeeuiRc::UIErrorMalformedUTF8Encoding) ==
+                uint32_t(ResponseCode::UIErrorMalformedUTF8Encoding),
+        "teeui::ResponseCode and "
+        "::android::hardware::confirmationui::V1_0::Responsecude are out of "
+        "sync");
+    return ResponseCode(trc);
+}
+
+teeui::UIOption convertUIOption(UIOption uio) {
+    static_assert(uint32_t(UIOption::AccessibilityInverted) ==
+                          uint32_t(teeui::UIOption::AccessibilityInverted) &&
+                      uint32_t(UIOption::AccessibilityMagnified) ==
+                          uint32_t(teeui::UIOption::AccessibilityMagnified),
+                  "teeui::UIOPtion and ::android::hardware::confirmationui::V1_0::UIOption "
+                  "anre out of sync");
+    return teeui::UIOption(uio);
+}
+
+inline MsgString hidl2MsgString(const hidl_string& s) {
+    return {s.c_str(), s.c_str() + s.size()};
+}
+template <typename T> inline MsgVector<T> hidl2MsgVector(const hidl_vec<T>& v) {
+    return {v};
+}
+
+inline MsgVector<teeui::UIOption> hidl2MsgVector(const hidl_vec<UIOption>& v) {
+    MsgVector<teeui::UIOption> result(v.size());
+    for (unsigned int i = 0; i < v.size(); ++i) {
+        result[i] = convertUIOption(v[i]);
+    }
+    return result;
+}
+
+}  // namespace
+
+TrustyConfirmationUI::TrustyConfirmationUI()
+    : listener_state_(ListenerState::None), prompt_result_(ResponseCode::Ignored) {}
+
+TrustyConfirmationUI::~TrustyConfirmationUI() {
+    ListenerState state = listener_state_;
+    if (state == ListenerState::SetupDone || state == ListenerState::Interactive) {
+        abort();
+    }
+    if (state != ListenerState::None) {
+        callback_thread_.join();
+    }
+}
+
+std::tuple<TeeuiRc, MsgVector<uint8_t>, MsgVector<uint8_t>>
+TrustyConfirmationUI::promptUserConfirmation_(const MsgString& promptText,
+                                              const MsgVector<uint8_t>& extraData,
+                                              const MsgString& locale,
+                                              const MsgVector<teeui::UIOption>& uiOptions) {
+    std::unique_lock<std::mutex> stateLock(listener_state_lock_);
+    /*
+     * This is the main listener thread function. The listener thread life cycle
+     * is equivalent to the life cycle of a single confirmation request. The life
+     * cycle is divided in four phases.
+     *  * The starting phase:
+     *    * The Trusted App gets loaded and/or the connection to it gets established.
+     *    * A connection to the secure input device is established.
+     *    * The prompt is initiated. This sends all information required by the
+     *      confirmation dialog to the TA. The dialog is not yet displayed.
+     *    * An event loop is created.
+     *      * The event loop listens for user input events, fetches them from the
+     *        secure input device, and delivers them to the TA.
+     *    * All evdev devices are grabbed to give confirmationui exclusive access
+     *      to user input.
+     *
+     * Note: During the starting phase the hwbinder service thread is blocked and
+     * waiting for possible Errors. If the setup phase concludes successfully, the
+     * hwbinder service thread gets unblocked and returns successfully. Errors
+     * that occur after the first phase are delivered by callback interface.
+     *
+     *  * The 2nd phase - non interactive phase
+     *    * The event loop thread is started.
+     *    * After a grace period:
+     *      * A handshake between the secure input device SecureInput and the TA
+     *        is performed.
+     *      * The input event handler are armed to process user input events.
+     *
+     *  * The 3rd phase - interactive phase
+     *    * We wait to any external event
+     *      * Abort
+     *      * Secure user input asserted
+     *      * Secure input delivered (for non interactive VTS testing)
+     *    * The result is fetched from the TA.
+     *
+     *  * The 4th phase - cleanup
+     *    The cleanup phase is given by the scope of automatic variables created
+     *    in this function. The cleanup commences in reverse order of their creation.
+     *    Here is a list of more complex items in the order in which they go out of
+     *    scope
+     *    * finalizeSecureTouch - signals and joins the secure touch thread.
+     *    * eventloop - signals and joins the event loop thread. The event
+     *      handlers also own all EventDev instances which ungrab the event devices.
+     *      When the eventloop goes out of scope the EventDevs get destroyed
+     *      relinquishing the exclusive hold on the event devices.
+     *    * finalizeConfirmationPrompt - calls abort on the TA, making sure a
+     *      pending operation gets canceled. If the prompt concluded successfully this
+     *      is a spurious call but semantically a no op.
+     *    * secureInput - shuts down the connection to the secure input device
+     *      SecureInput.
+     *    * app - disconnects the TA. Since app is a shared pointer this may not
+     *      unload the app here. It is possible that more instances of the shared
+     *      pointer are held in TrustyConfirmationUI::deliverSecureInputEvent and
+     *      TrustyConfirmationUI::abort. But these instances are extremely short lived
+     *      and it is safe if they are destroyed by either.
+     *    * stateLock - unlocks the listener_state_lock_ if it happens to be held
+     *      at the time of return.
+     */
+
+    std::tuple<TeeuiRc, MsgVector<uint8_t>, MsgVector<uint8_t>> result;
+    TeeuiRc& rc = std::get<TeeuiRc>(result);
+    rc = TeeuiRc::SystemError;
+
+    listener_state_ = ListenerState::Starting;
+
+    auto app = std::make_shared<TrustyApp>(kTrustyDeviceName, kConfirmationuiAppName);
+    if (!app) return result;  // TeeuiRc::SystemError
+
+    app_ = app;
+
+    auto hsBegin = [&]() -> std::tuple<TeeuiRc, Nonce> {
+        auto [error, result] =
+            app->issueCmd<secure_input::InputHandshake, secure_input::InputHandshakeResponse>();
+        auto& [rc, nCo] = result;
+
+        if (error != TrustyAppError::OK || rc != TeeuiRc::OK) {
+            LOG(ERROR) << "Failed to begin secure input handshake (" << int32_t(error) << "/"
+                       << uint32_t(rc) << ")";
+            rc = error != TrustyAppError::OK ? TeeuiRc::SystemError : rc;
+        }
+        return result;
+    };
+
+    auto hsFinalize = [&](const Signature& sig, const Nonce& nCi) -> TeeuiRc {
+        auto [error, finalizeResponse] =
+            app->issueCmd<FinalizeInputSessionHandshake, FinalizeInputSessionHandshakeResponse>(
+                nCi, sig);
+        auto& [rc] = finalizeResponse;
+        if (error != TrustyAppError::OK || rc != TeeuiRc::OK) {
+            LOG(ERROR) << "Failed to finalize secure input handshake (" << int32_t(error) << "/"
+                       << uint32_t(rc) << ")";
+            rc = error != TrustyAppError::OK ? TeeuiRc::SystemError : rc;
+        }
+        return rc;
+    };
+
+    auto deliverInput = [&](DTupKeyEvent event,
+                            const Signature& sig) -> std::tuple<TeeuiRc, InputResponse> {
+        auto [error, result] =
+            app->issueCmd<DeliverInputEvent, DeliverInputEventResponse>(event, sig);
+        auto& [rc, ir] = result;
+        if (error != TrustyAppError::OK) {
+            LOG(ERROR) << "Failed to deliver input command";
+            rc = TeeuiRc::SystemError;
+        }
+        return result;
+    };
+
+    std::atomic<TeeuiRc> eventRC = TeeuiRc::OperationPending;
+    auto inputResult = [&](TeeuiRc rc) {
+        TeeuiRc expected = TeeuiRc::OperationPending;
+        if (eventRC.compare_exchange_strong(expected, rc)) {
+            listener_state_condv_.notify_all();
+        }
+    };
+
+    // create Secure Input device.
+    auto secureInput = createSecureInput(hsBegin, hsFinalize, deliverInput, inputResult);
+    if (!secureInput || !(*secureInput)) {
+        LOG(ERROR) << "Failed to open secure input device";
+        return result;  // TeeuiRc::SystemError;
+    }
+
+    Finalize finalizeConfirmationPrompt([app] {
+        LOG(INFO) << "Calling abort for cleanup";
+        app->issueCmd<AbortMsg>();
+    });
+
+    // initiate prompt
+    LOG(INFO) << "Initiating prompt";
+    TrustyAppError error;
+    auto initResponse = std::tie(rc);
+    std::tie(error, initResponse) =
+        app->issueCmd<PromptUserConfirmationMsg, PromptUserConfirmationResponse>(
+            promptText, extraData, locale, uiOptions);
+    if (error == TrustyAppError::MSG_TOO_LONG) {
+        LOG(ERROR) << "PromptUserConfirmationMsg failed: message too long";
+        rc = TeeuiRc::UIErrorMessageTooLong;
+        return result;
+    } else if (error != TrustyAppError::OK) {
+        LOG(ERROR) << "PromptUserConfirmationMsg failed: " << int32_t(error);
+        return result;  // TeeuiRc::SystemError;
+    }
+    if (rc != TeeuiRc::OK) {
+        LOG(ERROR) << "PromptUserConfirmationMsg failed: " << uint32_t(rc);
+        return result;
+    }
+
+    LOG(INFO) << "Grabbing event devices";
+    EventLoop eventloop;
+    bool grabbed =
+        grabAllEvDevsAndRegisterCallbacks(&eventloop, [&](short flags, const EventDev& evDev) {
+            if (!(flags & POLLIN)) return;
+            secureInput->handleEvent(evDev);
+        });
+
+    if (!grabbed) {
+        rc = TeeuiRc::SystemError;
+        return result;
+    }
+
+    abort_called_ = false;
+    secureInputDelivered_ = false;
+
+    //  ############################## Start 2nd Phase #############################################
+    listener_state_ = ListenerState::SetupDone;
+    stateLock.unlock();
+    listener_state_condv_.notify_all();
+
+    if (!eventloop.start()) {
+        rc = TeeuiRc::SystemError;
+        return result;
+    }
+
+    stateLock.lock();
+
+    LOG(INFO) << "going to sleep for the grace period";
+    auto then = std::chrono::system_clock::now() +
+                std::chrono::milliseconds(kUserPreInputGracePeriodMillis) +
+                std::chrono::microseconds(50);
+    listener_state_condv_.wait_until(stateLock, then, [&]() { return abort_called_; });
+    LOG(INFO) << "waking up";
+
+    if (abort_called_) {
+        LOG(ERROR) << "Abort called";
+        result = {TeeuiRc::Aborted, {}, {}};
+        return result;
+    }
+
+    LOG(INFO) << "Arming event poller";
+    // tell the event poller to act on received input events from now on.
+    secureInput->start();
+
+    //  ############################## Start 3rd Phase - interactive phase #########################
+    LOG(INFO) << "Transition to Interactive";
+    listener_state_ = ListenerState::Interactive;
+    stateLock.unlock();
+    listener_state_condv_.notify_all();
+
+    stateLock.lock();
+    listener_state_condv_.wait(stateLock, [&]() {
+        return eventRC != TeeuiRc::OperationPending || abort_called_ || secureInputDelivered_;
+    });
+    LOG(INFO) << "Listener waking up";
+    if (abort_called_) {
+        LOG(ERROR) << "Abort called";
+        result = {TeeuiRc::Aborted, {}, {}};
+        return result;
+    }
+
+    if (!secureInputDelivered_) {
+        if (eventRC != TeeuiRc::OK) {
+            LOG(ERROR) << "Bad input response";
+            result = {eventRC, {}, {}};
+            return result;
+        }
+    }
+
+    stateLock.unlock();
+
+    LOG(INFO) << "Fetching Result";
+    std::tie(error, result) = app->issueCmd<FetchConfirmationResult, ResultMsg>();
+    LOG(INFO) << "Result yields " << int32_t(error) << "/" << uint32_t(rc);
+    if (error != TrustyAppError::OK) {
+        result = {TeeuiRc::SystemError, {}, {}};
+    }
+    return result;
+
+    //  ############################## Start 4th Phase - cleanup ##################################
+}
+
+// Methods from ::android::hardware::confirmationui::V1_0::IConfirmationUI
+// follow.
+Return<ResponseCode> TrustyConfirmationUI::promptUserConfirmation(
+    const sp<IConfirmationResultCallback>& resultCB, const hidl_string& promptText,
+    const hidl_vec<uint8_t>& extraData, const hidl_string& locale,
+    const hidl_vec<UIOption>& uiOptions) {
+    std::unique_lock<std::mutex> stateLock(listener_state_lock_, std::defer_lock);
+    if (!stateLock.try_lock()) {
+        return ResponseCode::OperationPending;
+    }
+    switch (listener_state_) {
+    case ListenerState::None:
+        break;
+    case ListenerState::Starting:
+    case ListenerState::SetupDone:
+    case ListenerState::Interactive:
+        return ResponseCode::OperationPending;
+    case ListenerState::Terminating:
+        callback_thread_.join();
+        listener_state_ = ListenerState::None;
+        break;
+    default:
+        return ResponseCode::Unexpected;
+    }
+
+    assert(listener_state_ == ListenerState::None);
+
+    callback_thread_ = std::thread(
+        [this](sp<IConfirmationResultCallback> resultCB, hidl_string promptText,
+               hidl_vec<uint8_t> extraData, hidl_string locale, hidl_vec<UIOption> uiOptions) {
+            auto [trc, msg, token] =
+                promptUserConfirmation_(hidl2MsgString(promptText), hidl2MsgVector(extraData),
+                                        hidl2MsgString(locale), hidl2MsgVector(uiOptions));
+            bool do_callback = (listener_state_ == ListenerState::Interactive ||
+                                listener_state_ == ListenerState::SetupDone) &&
+                               resultCB;
+            prompt_result_ = convertRc(trc);
+            listener_state_ = ListenerState::Terminating;
+            if (do_callback) {
+                auto error = resultCB->result(prompt_result_, msg, token);
+                if (!error.isOk()) {
+                    LOG(ERROR) << "Result callback failed " << error.description();
+                }
+            } else {
+                listener_state_condv_.notify_all();
+            }
+        },
+        resultCB, promptText, extraData, locale, uiOptions);
+
+    listener_state_condv_.wait(stateLock, [this] {
+        return listener_state_ == ListenerState::SetupDone ||
+               listener_state_ == ListenerState::Interactive ||
+               listener_state_ == ListenerState::Terminating;
+    });
+    if (listener_state_ == ListenerState::Terminating) {
+        callback_thread_.join();
+        listener_state_ = ListenerState::None;
+        return prompt_result_;
+    }
+    return ResponseCode::OK;
+}
+
+Return<ResponseCode>
+TrustyConfirmationUI::deliverSecureInputEvent(const HardwareAuthToken& secureInputToken) {
+    ResponseCode rc = ResponseCode::Ignored;
+    {
+        /*
+         * deliverSecureInputEvent is only used by the VTS test to mock human input. A correct
+         * implementation responds with a mock confirmation token signed with a test key. The
+         * problem is that the non interactive grace period was not formalized in the HAL spec,
+         * so that the VTS test does not account for the grace period. (It probably should.)
+         * This means we can only pass the VTS test if we block until the grace period is over
+         * (SetupDone -> Interactive) before we deliver the input event.
+         *
+         * The true secure input is delivered by a different mechanism and gets ignored -
+         * not queued - until the grace period is over.
+         *
+         */
+        std::unique_lock<std::mutex> stateLock(listener_state_lock_);
+        listener_state_condv_.wait(stateLock,
+                                   [this] { return listener_state_ != ListenerState::SetupDone; });
+
+        if (listener_state_ != ListenerState::Interactive) return ResponseCode::Ignored;
+        auto sapp = app_.lock();
+        if (!sapp) return ResponseCode::Ignored;
+        auto [error, response] =
+            sapp->issueCmd<DeliverTestCommandMessage, DeliverTestCommandResponse>(
+                static_cast<teeui::TestModeCommands>(secureInputToken.challenge));
+        if (error != TrustyAppError::OK) return ResponseCode::SystemError;
+        auto& [trc] = response;
+        if (trc != TeeuiRc::Ignored) secureInputDelivered_ = true;
+        rc = convertRc(trc);
+    }
+    if (secureInputDelivered_) listener_state_condv_.notify_all();
+    // VTS test expect an OK response if the event was successfully delivered.
+    // But since the TA returns the callback response now, we have to translate
+    // Canceled into OK. Canceled is only returned if the delivered event canceled
+    // the operation, which means that the event was successfully delivered. Thus
+    // we return OK.
+    if (rc == ResponseCode::Canceled) return ResponseCode::OK;
+    return rc;
+}
+
+Return<void> TrustyConfirmationUI::abort() {
+    {
+        std::unique_lock<std::mutex> stateLock(listener_state_lock_);
+        if (listener_state_ == ListenerState::SetupDone ||
+            listener_state_ == ListenerState::Interactive) {
+            auto sapp = app_.lock();
+            if (sapp) sapp->issueCmd<AbortMsg>();
+            abort_called_ = true;
+        }
+    }
+    listener_state_condv_.notify_all();
+    return Void();
+}
+
+android::sp<IConfirmationUI> createTrustyConfirmationUI() {
+    return new TrustyConfirmationUI();
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace confirmationui
+}  // namespace hardware
+}  // namespace android
diff --git a/guest/hals/confirmationui/TrustyConfirmationUI.h b/guest/hals/confirmationui/TrustyConfirmationUI.h
new file mode 100644
index 0000000..0bd703c
--- /dev/null
+++ b/guest/hals/confirmationui/TrustyConfirmationUI.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H
+#define ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H
+
+#include <android/hardware/confirmationui/1.0/IConfirmationUI.h>
+#include <android/hardware/keymaster/4.0/types.h>
+#include <hidl/Status.h>
+
+#include <atomic>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <teeui/generic_messages.h>
+#include <thread>
+
+#include "TrustyApp.h"
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::sp;
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+
+using ::android::trusty::confirmationui::TrustyApp;
+
+class TrustyConfirmationUI : public IConfirmationUI {
+  public:
+    TrustyConfirmationUI();
+    virtual ~TrustyConfirmationUI();
+    // Methods from ::android::hardware::confirmationui::V1_0::IConfirmationUI
+    // follow.
+    Return<ResponseCode> promptUserConfirmation(const sp<IConfirmationResultCallback>& resultCB,
+                                                const hidl_string& promptText,
+                                                const hidl_vec<uint8_t>& extraData,
+                                                const hidl_string& locale,
+                                                const hidl_vec<UIOption>& uiOptions) override;
+    Return<ResponseCode> deliverSecureInputEvent(
+        const ::android::hardware::keymaster::V4_0::HardwareAuthToken& secureInputToken) override;
+    Return<void> abort() override;
+
+  private:
+    std::weak_ptr<TrustyApp> app_;
+    std::thread callback_thread_;
+
+    enum class ListenerState : uint32_t {
+        None,
+        Starting,
+        SetupDone,
+        Interactive,
+        Terminating,
+    };
+
+    /*
+     * listener_state is protected by listener_state_lock. It makes transitions between phases
+     * of the confirmation operation atomic.
+     * (See TrustyConfirmationUI.cpp#promptUserConfirmation_ for details about operation phases)
+     */
+    ListenerState listener_state_;
+    /*
+     * abort_called_ is also protected by listener_state_lock_ and indicates that the HAL user
+     * called abort.
+     */
+    bool abort_called_;
+    std::mutex listener_state_lock_;
+    std::condition_variable listener_state_condv_;
+    ResponseCode prompt_result_;
+    bool secureInputDelivered_;
+
+    std::tuple<teeui::ResponseCode, teeui::MsgVector<uint8_t>, teeui::MsgVector<uint8_t>>
+    promptUserConfirmation_(const teeui::MsgString& promptText,
+                            const teeui::MsgVector<uint8_t>& extraData,
+                            const teeui::MsgString& locale,
+                            const teeui::MsgVector<teeui::UIOption>& uiOptions);
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace confirmationui
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H
diff --git a/guest/hals/confirmationui/android.hardware.confirmationui@1.0-service.cuttlefish.rc b/guest/hals/confirmationui/android.hardware.confirmationui@1.0-service.cuttlefish.rc
new file mode 100644
index 0000000..81dfd49
--- /dev/null
+++ b/guest/hals/confirmationui/android.hardware.confirmationui@1.0-service.cuttlefish.rc
@@ -0,0 +1,5 @@
+service confirmationui-1-0 /vendor/bin/hw/android.hardware.confirmationui@1.0-service.cuttlefish
+    interface android.hardware.confirmationui@1.0::IConfirmationUI default
+    class hal
+    user system
+    group drmrpc input system
diff --git a/guest/hals/confirmationui/android.hardware.confirmationui@1.0-service.cuttlefish.xml b/guest/hals/confirmationui/android.hardware.confirmationui@1.0-service.cuttlefish.xml
new file mode 100644
index 0000000..9008b87
--- /dev/null
+++ b/guest/hals/confirmationui/android.hardware.confirmationui@1.0-service.cuttlefish.xml
@@ -0,0 +1,11 @@
+<manifest version="1.0" type="device">
+    <hal format="hidl">
+        <name>android.hardware.confirmationui</name>
+        <transport>hwbinder</transport>
+        <version>1.0</version>
+        <interface>
+        <name>IConfirmationUI</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+</manifest>
diff --git a/guest/hals/confirmationui/include/TrustyConfirmationuiHal.h b/guest/hals/confirmationui/include/TrustyConfirmationuiHal.h
new file mode 100644
index 0000000..2ab9389
--- /dev/null
+++ b/guest/hals/confirmationui/include/TrustyConfirmationuiHal.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 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 <android/hardware/confirmationui/1.0/IConfirmationUI.h>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+
+android::sp<IConfirmationUI> createTrustyConfirmationUI();
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace confirmationui
+}  // namespace hardware
+}  // namespace android
diff --git a/guest/hals/confirmationui/include/TrustyIpc.h b/guest/hals/confirmationui/include/TrustyIpc.h
new file mode 100644
index 0000000..eb764bc
--- /dev/null
+++ b/guest/hals/confirmationui/include/TrustyIpc.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/*
+ * This interface is shared between Android and Trusty. There is a copy in each
+ * repository. They must be kept in sync.
+ */
+
+#define CONFIRMATIONUI_PORT "com.android.trusty.confirmationui"
+
+/**
+ * enum confirmationui_cmd - command identifiers for ConfirmationUI interface
+ * @CONFIRMATIONUI_RESP_BIT:  response bit set as part of response
+ * @CONFIRMATIONUI_REQ_SHIFT: number of bits used by response bit
+ * @CONFIRMATIONUI_CMD_INIT:  command to initialize session
+ * @CONFIRMATIONUI_CMD_MSG:   command to send ConfirmationUI messages
+ */
+enum confirmationui_cmd : uint32_t {
+    CONFIRMATIONUI_RESP_BIT = 1,
+    CONFIRMATIONUI_REQ_SHIFT = 1,
+
+    CONFIRMATIONUI_CMD_INIT = (1 << CONFIRMATIONUI_REQ_SHIFT),
+    CONFIRMATIONUI_CMD_MSG = (2 << CONFIRMATIONUI_REQ_SHIFT),
+};
+
+/**
+ * struct confirmationui_hdr - header for ConfirmationUI messages
+ * @cmd: command identifier
+ *
+ * Note that no messages return a status code. Any error on the server side
+ * results in the connection being closed. So, operations can be assumed to be
+ * successful if they return a response.
+ */
+struct confirmationui_hdr {
+    uint32_t cmd;
+};
+
+/**
+ * struct confirmationui_init_req - arguments for request to initialize a
+ *                                  session
+ * @shm_len: length of memory region being shared
+ *
+ * A handle to a memory region must be sent along with this message. This memory
+ * is send to ConfirmationUI messages.
+ */
+struct confirmationui_init_req {
+    uint32_t shm_len;
+};
+
+/**
+ * struct confirmationui_msg_args - arguments for sending a message
+ * @msg_len: length of message being sent
+ *
+ * Contents of the message are located in the shared memory region that is
+ * established using %CONFIRMATIONUI_CMD_INIT.
+ *
+ * ConfirmationUI messages can travel both ways.
+ */
+struct confirmationui_msg_args {
+    uint32_t msg_len;
+};
+
+#define CONFIRMATIONUI_MAX_MSG_SIZE 0x2000
diff --git a/guest/hals/confirmationui/service.cpp b/guest/hals/confirmationui/service.cpp
new file mode 100644
index 0000000..dd7e84b
--- /dev/null
+++ b/guest/hals/confirmationui/service.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright 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.
+ */
+
+#include <android-base/logging.h>
+#include <hidl/HidlTransportSupport.h>
+
+#include <TrustyConfirmationuiHal.h>
+
+using android::sp;
+using android::hardware::confirmationui::V1_0::implementation::createTrustyConfirmationUI;
+
+int main() {
+    ::android::hardware::configureRpcThreadpool(1, true /*willJoinThreadpool*/);
+    auto service = createTrustyConfirmationUI();
+    auto status = service->registerAsService();
+    if (status != android::OK) {
+        LOG(FATAL) << "Could not register service for ConfirmationUI 1.0 (" << status << ")";
+        return -1;
+    }
+    ::android::hardware::joinRpcThreadpool();
+    return -1;
+}
diff --git a/guest/hals/health/storage/Android.bp b/guest/hals/health/storage/Android.bp
index dc57d0f..1ca807d1 100644
--- a/guest/hals/health/storage/Android.bp
+++ b/guest/hals/health/storage/Android.bp
@@ -38,7 +38,7 @@
     ],
 
     shared_libs: [
-        "android.hardware.health.storage-V1-ndk_platform",
+        "android.hardware.health.storage-V1-ndk",
         "libbase",
         "libbinder_ndk",
         "libutils",
diff --git a/guest/hals/keymint/remote/Android.bp b/guest/hals/keymint/remote/Android.bp
index bb78ff2..26070b4 100644
--- a/guest/hals/keymint/remote/Android.bp
+++ b/guest/hals/keymint/remote/Android.bp
@@ -32,9 +32,10 @@
         "-Wextra",
     ],
     shared_libs: [
-        "android.hardware.security.keymint-V1-ndk_platform",
-        "android.hardware.security.sharedsecret-V1-ndk_platform",
-        "android.hardware.security.secureclock-V1-ndk_platform",
+        "android.hardware.security.keymint-V1-ndk",
+        "android.hardware.security.secureclock-V1-ndk",
+        "android.hardware.security.sharedsecret-V1-ndk",
+        "lib_android_keymaster_keymint_utils",
         "libbase",
         "libbinder_ndk",
         "libcppbor_external",
@@ -42,8 +43,8 @@
         "libcuttlefish_fs",
         "libcuttlefish_security",
         "libhardware",
-        "libkeymint",
         "libkeymaster_messages",
+        "libkeymint",
         "liblog",
         "libutils",
     ],
@@ -51,10 +52,15 @@
         "remote_keymint_device.cpp",
         "remote_keymint_operation.cpp",
         "remote_keymaster.cpp",
+        "remote_remotely_provisioned_component.cpp",
         "remote_secure_clock.cpp",
+        "remote_shared_secret.cpp",
         "service.cpp",
     ],
     defaults: [
         "cuttlefish_guest_only",
     ],
+    required: [
+        "RemoteProvisioner",
+    ],
 }
diff --git a/guest/hals/keymint/remote/remote_keymaster.cpp b/guest/hals/keymint/remote/remote_keymaster.cpp
index 4a37cf0..d67b837 100644
--- a/guest/hals/keymint/remote/remote_keymaster.cpp
+++ b/guest/hals/keymint/remote/remote_keymaster.cpp
@@ -22,8 +22,9 @@
 
 namespace keymaster {
 
-RemoteKeymaster::RemoteKeymaster(cuttlefish::KeymasterChannel* channel)
-    : channel_(channel) {}
+RemoteKeymaster::RemoteKeymaster(cuttlefish::KeymasterChannel* channel,
+                                 uint32_t message_version)
+    : channel_(channel), message_version_(message_version) {}
 
 RemoteKeymaster::~RemoteKeymaster() {}
 
@@ -66,6 +67,28 @@
     return false;
   }
 
+  // Set the vendor patchlevel to value retrieved from system property (which
+  // requires SELinux permission).
+  ConfigureVendorPatchlevelRequest vendor_req(message_version());
+  vendor_req.vendor_patchlevel = GetVendorPatchlevel();
+  ConfigureVendorPatchlevelResponse vendor_rsp =
+      ConfigureVendorPatchlevel(vendor_req);
+  if (vendor_rsp.error != KM_ERROR_OK) {
+    LOG(ERROR) << "Failed to configure keymaster vendor patchlevel: "
+               << vendor_rsp.error;
+    return false;
+  }
+
+  // Set the boot patchlevel to zero for Cuttlefish.
+  ConfigureBootPatchlevelRequest boot_req(message_version());
+  boot_req.boot_patchlevel = 0;
+  ConfigureBootPatchlevelResponse boot_rsp = ConfigureBootPatchlevel(boot_req);
+  if (boot_rsp.error != KM_ERROR_OK) {
+    LOG(ERROR) << "Failed to configure keymaster boot patchlevel: "
+               << boot_rsp.error;
+    return false;
+  }
+
   return true;
 }
 
@@ -121,15 +144,25 @@
 
 void RemoteKeymaster::GenerateKey(const GenerateKeyRequest& request,
                                   GenerateKeyResponse* response) {
-  GenerateKeyRequest datedRequest(request.message_version);
-  datedRequest.key_description = request.key_description;
-
-  if (!request.key_description.Contains(TAG_CREATION_DATETIME)) {
+  if (message_version_ < MessageVersion(KmVersion::KEYMINT_1) &&
+      !request.key_description.Contains(TAG_CREATION_DATETIME)) {
+    GenerateKeyRequest datedRequest(request.message_version);
     datedRequest.key_description.push_back(TAG_CREATION_DATETIME,
                                            java_time(time(NULL)));
+    ForwardCommand(GENERATE_KEY, datedRequest, response);
+  } else {
+    ForwardCommand(GENERATE_KEY, request, response);
   }
+}
 
-  ForwardCommand(GENERATE_KEY, datedRequest, response);
+void RemoteKeymaster::GenerateRkpKey(const GenerateRkpKeyRequest& request,
+                                     GenerateRkpKeyResponse* response) {
+  ForwardCommand(GENERATE_RKP_KEY, request, response);
+}
+
+void RemoteKeymaster::GenerateCsr(const GenerateCsrRequest& request,
+                                  GenerateCsrResponse* response) {
+  ForwardCommand(GENERATE_CSR, request, response);
 }
 
 void RemoteKeymaster::GetKeyCharacteristics(
@@ -230,10 +263,24 @@
   return response;
 }
 
-void RemoteKeymaster::GenerateTimestampToken(GenerateTimestampTokenRequest&,
-                                             GenerateTimestampTokenResponse*) {
-  // TODO(aosp/1641315): Send a message to the host.
-  // ForwardCommand(GENERATE_TIMESTAMP_TOKEN, request, response);
+void RemoteKeymaster::GenerateTimestampToken(
+    GenerateTimestampTokenRequest& request,
+    GenerateTimestampTokenResponse* response) {
+  ForwardCommand(GENERATE_TIMESTAMP_TOKEN, request, response);
+}
+
+ConfigureVendorPatchlevelResponse RemoteKeymaster::ConfigureVendorPatchlevel(
+    const ConfigureVendorPatchlevelRequest& request) {
+  ConfigureVendorPatchlevelResponse response(message_version());
+  ForwardCommand(CONFIGURE_VENDOR_PATCHLEVEL, request, &response);
+  return response;
+}
+
+ConfigureBootPatchlevelResponse RemoteKeymaster::ConfigureBootPatchlevel(
+    const ConfigureBootPatchlevelRequest& request) {
+  ConfigureBootPatchlevelResponse response(message_version());
+  ForwardCommand(CONFIGURE_BOOT_PATCHLEVEL, request, &response);
+  return response;
 }
 
 }  // namespace keymaster
diff --git a/guest/hals/keymint/remote/remote_keymaster.h b/guest/hals/keymint/remote/remote_keymaster.h
index 6cc6c7b..d7c7abf 100644
--- a/guest/hals/keymint/remote/remote_keymaster.h
+++ b/guest/hals/keymint/remote/remote_keymaster.h
@@ -26,12 +26,14 @@
 class RemoteKeymaster {
  private:
   cuttlefish::KeymasterChannel* channel_;
+  const uint32_t message_version_;
 
   void ForwardCommand(AndroidKeymasterCommand command, const Serializable& req,
                       KeymasterResponse* rsp);
 
  public:
-  RemoteKeymaster(cuttlefish::KeymasterChannel*);
+  RemoteKeymaster(cuttlefish::KeymasterChannel*,
+                  uint32_t message_version = kDefaultMessageVersion);
   ~RemoteKeymaster();
   bool Initialize();
   void GetVersion(const GetVersionRequest& request,
@@ -53,6 +55,10 @@
   void Configure(const ConfigureRequest& request, ConfigureResponse* response);
   void GenerateKey(const GenerateKeyRequest& request,
                    GenerateKeyResponse* response);
+  void GenerateRkpKey(const GenerateRkpKeyRequest& request,
+                      GenerateRkpKeyResponse* response);
+  void GenerateCsr(const GenerateCsrRequest& request,
+                   GenerateCsrResponse* response);
   void GetKeyCharacteristics(const GetKeyCharacteristicsRequest& request,
                              GetKeyCharacteristicsResponse* response);
   void ImportKey(const ImportKeyRequest& request, ImportKeyResponse* response);
@@ -80,12 +86,16 @@
       const VerifyAuthorizationRequest& request);
   DeviceLockedResponse DeviceLocked(const DeviceLockedRequest& request);
   EarlyBootEndedResponse EarlyBootEnded();
+  ConfigureVendorPatchlevelResponse ConfigureVendorPatchlevel(
+      const ConfigureVendorPatchlevelRequest& request);
+  ConfigureBootPatchlevelResponse ConfigureBootPatchlevel(
+      const ConfigureBootPatchlevelRequest& request);
   void GenerateTimestampToken(GenerateTimestampTokenRequest& request,
                               GenerateTimestampTokenResponse* response);
 
   // CF HAL and remote sides are always compiled together, so will never
   // disagree about message versions.
-  uint32_t message_version() { return kDefaultMessageVersion; }
+  uint32_t message_version() { return message_version_; }
 };
 
 }  // namespace keymaster
diff --git a/guest/hals/keymint/remote/remote_keymint_device.cpp b/guest/hals/keymint/remote/remote_keymint_device.cpp
index c9171c8..bef3230 100644
--- a/guest/hals/keymint/remote/remote_keymint_device.cpp
+++ b/guest/hals/keymint/remote/remote_keymint_device.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "android.hardware.security.keymint-impl"
+#define LOG_TAG "android.hardware.security.keymint-impl.remote"
 #include <android-base/logging.h>
 
 #include "guest/hals/keymint/remote/remote_keymint_device.h"
@@ -37,17 +37,19 @@
 namespace {
 
 vector<KeyCharacteristics> convertKeyCharacteristics(
-    SecurityLevel keyMintSecurityLevel, const AuthorizationSet& sw_enforced,
-    const AuthorizationSet& hw_enforced) {
+    const vector<KeyParameter>& keyParams, SecurityLevel keyMintSecurityLevel,
+    const AuthorizationSet& sw_enforced, const AuthorizationSet& hw_enforced,
+    bool include_keystore_enforced = true) {
   KeyCharacteristics keyMintEnforced{keyMintSecurityLevel, {}};
 
   if (keyMintSecurityLevel != SecurityLevel::SOFTWARE) {
     // We're pretending to be TRUSTED_ENVIRONMENT or STRONGBOX.
     keyMintEnforced.authorizations = kmParamSet2Aidl(hw_enforced);
-    // Put all the software authorizations in the keystore list.
-    KeyCharacteristics keystoreEnforced{SecurityLevel::KEYSTORE,
-                                        kmParamSet2Aidl(sw_enforced)};
-    return {std::move(keyMintEnforced), std::move(keystoreEnforced)};
+    if (include_keystore_enforced && !sw_enforced.empty()) {
+      return {std::move(keyMintEnforced),
+              {SecurityLevel::KEYSTORE, kmParamSet2Aidl(sw_enforced)}};
+    }
+    return {std::move(keyMintEnforced)};
   }
 
   KeyCharacteristics keystoreEnforced{SecurityLevel::KEYSTORE, {}};
@@ -71,15 +73,17 @@
       /* Unimplemented */
       case KM_TAG_ALLOW_WHILE_ON_BODY:
       case KM_TAG_BOOTLOADER_ONLY:
-      case KM_TAG_EARLY_BOOT_ONLY:
       case KM_TAG_ROLLBACK_RESISTANT:
       case KM_TAG_STORAGE_KEY:
-      case KM_TAG_TRUSTED_CONFIRMATION_REQUIRED:
-      case KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED:
         break;
 
       /* Unenforceable */
       case KM_TAG_CREATION_DATETIME:
+        for (const auto& p : keyParams) {
+          if (p.tag == Tag::CREATION_DATETIME) {
+            keystoreEnforced.authorizations.push_back(kmParam2Aidl(entry));
+          }
+        }
         break;
 
       /* Disallowed in KeyCharacteristics */
@@ -122,6 +126,7 @@
       case KM_TAG_BOOT_PATCHLEVEL:
       case KM_TAG_CALLER_NONCE:
       case KM_TAG_DIGEST:
+      case KM_TAG_EARLY_BOOT_ONLY:
       case KM_TAG_EC_CURVE:
       case KM_TAG_EXPORTABLE:
       case KM_TAG_INCLUDE_UNIQUE_ID:
@@ -140,6 +145,8 @@
       case KM_TAG_UNLOCKED_DEVICE_REQUIRED:
       case KM_TAG_USER_AUTH_TYPE:
       case KM_TAG_USER_SECURE_ID:
+      case KM_TAG_TRUSTED_CONFIRMATION_REQUIRED:
+      case KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED:
       case KM_TAG_VENDOR_PATCHLEVEL:
         keyMintEnforced.authorizations.push_back(kmParam2Aidl(entry));
         break;
@@ -160,10 +167,12 @@
 
   vector<KeyCharacteristics> retval;
   retval.reserve(2);
-  if (!keyMintEnforced.authorizations.empty())
+  if (!keyMintEnforced.authorizations.empty()) {
     retval.push_back(std::move(keyMintEnforced));
-  if (!keystoreEnforced.authorizations.empty())
+  }
+  if (include_keystore_enforced && !keystoreEnforced.authorizations.empty()) {
     retval.push_back(std::move(keystoreEnforced));
+  }
 
   return retval;
 }
@@ -245,7 +254,7 @@
 
   creationResult->keyBlob = kmBlob2vector(response.key_blob);
   creationResult->keyCharacteristics = convertKeyCharacteristics(
-      securityLevel_, response.unenforced, response.enforced);
+      keyParams, securityLevel_, response.unenforced, response.enforced);
   creationResult->certificateChain =
       convertCertificateChain(response.certificate_chain);
   return ScopedAStatus::ok();
@@ -279,7 +288,7 @@
 
   creationResult->keyBlob = kmBlob2vector(response.key_blob);
   creationResult->keyCharacteristics = convertKeyCharacteristics(
-      securityLevel_, response.unenforced, response.enforced);
+      keyParams, securityLevel_, response.unenforced, response.enforced);
   creationResult->certificateChain =
       convertCertificateChain(response.certificate_chain);
 
@@ -310,7 +319,7 @@
 
   creationResult->keyBlob = kmBlob2vector(response.key_blob);
   creationResult->keyCharacteristics = convertKeyCharacteristics(
-      securityLevel_, response.unenforced, response.enforced);
+      unwrappingParams, securityLevel_, response.unenforced, response.enforced);
   creationResult->certificateChain =
       convertCertificateChain(response.certificate_chain);
 
@@ -358,11 +367,10 @@
   return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
 }
 
-ScopedAStatus RemoteKeyMintDevice::begin(KeyPurpose purpose,
-                                         const vector<uint8_t>& keyBlob,
-                                         const vector<KeyParameter>& params,
-                                         const HardwareAuthToken& authToken,
-                                         BeginResult* result) {
+ScopedAStatus RemoteKeyMintDevice::begin(
+    KeyPurpose purpose, const vector<uint8_t>& keyBlob,
+    const vector<KeyParameter>& params,
+    const optional<HardwareAuthToken>& authToken, BeginResult* result) {
   BeginOperationRequest request(impl_.message_version());
   request.purpose = legacy_enum_conversion(purpose);
   request.SetKeyMaterial(keyBlob.data(), keyBlob.size());
@@ -413,9 +421,26 @@
   return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
 }
 
-ScopedAStatus RemoteKeyMintDevice::performOperation(
-    const vector<uint8_t>& /* request */, vector<uint8_t>* /* response */) {
-  return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
+ScopedAStatus RemoteKeyMintDevice::getKeyCharacteristics(
+    const std::vector<uint8_t>& storageKeyBlob,
+    const std::vector<uint8_t>& appId, const std::vector<uint8_t>& appData,
+    std::vector<KeyCharacteristics>* keyCharacteristics) {
+  GetKeyCharacteristicsRequest request(impl_.message_version());
+  request.SetKeyMaterial(storageKeyBlob.data(), storageKeyBlob.size());
+  addClientAndAppData(appId, appData, &request.additional_params);
+
+  GetKeyCharacteristicsResponse response(impl_.message_version());
+  impl_.GetKeyCharacteristics(request, &response);
+
+  if (response.error != KM_ERROR_OK) {
+    return kmError2ScopedAStatus(response.error);
+  }
+
+  *keyCharacteristics = convertKeyCharacteristics(
+      {} /*keyParams*/, securityLevel_, response.unenforced, response.enforced,
+      false /*include_keystore_enforced*/);
+
+  return ScopedAStatus::ok();
 }
 
 }  // namespace aidl::android::hardware::security::keymint
diff --git a/guest/hals/keymint/remote/remote_keymint_device.h b/guest/hals/keymint/remote/remote_keymint_device.h
index 7f289b4..c3ebad1 100644
--- a/guest/hals/keymint/remote/remote_keymint_device.h
+++ b/guest/hals/keymint/remote/remote_keymint_device.h
@@ -65,7 +65,7 @@
 
   ScopedAStatus begin(KeyPurpose purpose, const vector<uint8_t>& keyBlob,
                       const vector<KeyParameter>& params,
-                      const HardwareAuthToken& authToken,
+                      const optional<HardwareAuthToken>& authToken,
                       BeginResult* result) override;
 
   ScopedAStatus deviceLocked(
@@ -77,8 +77,10 @@
       const std::vector<uint8_t>& storageKeyBlob,
       std::vector<uint8_t>* ephemeralKeyBlob) override;
 
-  ScopedAStatus performOperation(const vector<uint8_t>& request,
-                                 vector<uint8_t>* response) override;
+  ScopedAStatus getKeyCharacteristics(
+      const std::vector<uint8_t>& storageKeyBlob,
+      const std::vector<uint8_t>& appId, const std::vector<uint8_t>& appData,
+      std::vector<KeyCharacteristics>* keyCharacteristics) override;
 
  protected:
   ::keymaster::RemoteKeymaster& impl_;
diff --git a/guest/hals/keymint/remote/remote_keymint_operation.cpp b/guest/hals/keymint/remote/remote_keymint_operation.cpp
index a5fb6aa..b88715a 100644
--- a/guest/hals/keymint/remote/remote_keymint_operation.cpp
+++ b/guest/hals/keymint/remote/remote_keymint_operation.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "android.hardware.security.keymint-impl"
+#define LOG_TAG "android.hardware.security.keymint-impl.remote"
 #include <log/log.h>
 
 #include "guest/hals/keymint/remote/remote_keymint_operation.h"
diff --git a/guest/hals/keymint/remote/remote_remotely_provisioned_component.cpp b/guest/hals/keymint/remote/remote_remotely_provisioned_component.cpp
new file mode 100644
index 0000000..7d2eca1
--- /dev/null
+++ b/guest/hals/keymint/remote/remote_remotely_provisioned_component.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <keymaster/cppcose/cppcose.h>
+#include <keymaster/keymaster_configuration.h>
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+
+#include <variant>
+
+#include "KeyMintUtils.h"
+#include "android/binder_auto_utils.h"
+#include "remote_remotely_provisioned_component.h"
+
+namespace aidl::android::hardware::security::keymint {
+namespace {
+using namespace cppcose;
+using namespace keymaster;
+
+using ::aidl::android::hardware::security::keymint::km_utils::kmBlob2vector;
+using ::ndk::ScopedAStatus;
+
+// Error codes from the provisioning stack are negated.
+ndk::ScopedAStatus toKeymasterError(const KeymasterResponse& response) {
+  auto error =
+      static_cast<keymaster_error_t>(-static_cast<int32_t>(response.error));
+  return ::aidl::android::hardware::security::keymint::km_utils::
+      kmError2ScopedAStatus(error);
+}
+
+}  // namespace
+
+RemoteRemotelyProvisionedComponent::RemoteRemotelyProvisionedComponent(
+    keymaster::RemoteKeymaster& impl)
+    : impl_(impl) {}
+
+ScopedAStatus RemoteRemotelyProvisionedComponent::getHardwareInfo(
+    RpcHardwareInfo* info) {
+  info->versionNumber = 1;
+  info->rpcAuthorName = "Google";
+  info->supportedEekCurve = RpcHardwareInfo::CURVE_25519;
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteRemotelyProvisionedComponent::generateEcdsaP256KeyPair(
+    bool testMode, MacedPublicKey* macedPublicKey,
+    std::vector<uint8_t>* privateKeyHandle) {
+  GenerateRkpKeyRequest request(impl_.message_version());
+  request.test_mode = testMode;
+  GenerateRkpKeyResponse response(impl_.message_version());
+  impl_.GenerateRkpKey(request, &response);
+  if (response.error != KM_ERROR_OK) {
+    return toKeymasterError(response);
+  }
+
+  macedPublicKey->macedKey = km_utils::kmBlob2vector(response.maced_public_key);
+  *privateKeyHandle = km_utils::kmBlob2vector(response.key_blob);
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteRemotelyProvisionedComponent::generateCertificateRequest(
+    bool testMode, const std::vector<MacedPublicKey>& keysToSign,
+    const std::vector<uint8_t>& endpointEncCertChain,
+    const std::vector<uint8_t>& challenge, DeviceInfo* deviceInfo,
+    ProtectedData* protectedData, std::vector<uint8_t>* keysToSignMac) {
+  GenerateCsrRequest request(impl_.message_version());
+  request.test_mode = testMode;
+  request.num_keys = keysToSign.size();
+  request.keys_to_sign_array = new KeymasterBlob[keysToSign.size()];
+  for (size_t i = 0; i < keysToSign.size(); i++) {
+    request.SetKeyToSign(i, keysToSign[i].macedKey.data(),
+                         keysToSign[i].macedKey.size());
+  }
+  request.SetEndpointEncCertChain(endpointEncCertChain.data(),
+                                  endpointEncCertChain.size());
+  request.SetChallenge(challenge.data(), challenge.size());
+  GenerateCsrResponse response(impl_.message_version());
+  impl_.GenerateCsr(request, &response);
+
+  if (response.error != KM_ERROR_OK) {
+    return toKeymasterError(response);
+  }
+  deviceInfo->deviceInfo = km_utils::kmBlob2vector(response.device_info_blob);
+  protectedData->protectedData =
+      km_utils::kmBlob2vector(response.protected_data_blob);
+  *keysToSignMac = km_utils::kmBlob2vector(response.keys_to_sign_mac);
+  return ScopedAStatus::ok();
+}
+
+}  // namespace aidl::android::hardware::security::keymint
diff --git a/guest/hals/keymint/remote/remote_remotely_provisioned_component.h b/guest/hals/keymint/remote/remote_remotely_provisioned_component.h
new file mode 100644
index 0000000..35e182a
--- /dev/null
+++ b/guest/hals/keymint/remote/remote_remotely_provisioned_component.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <vector>
+
+#include <aidl/android/hardware/security/keymint/BnRemotelyProvisionedComponent.h>
+#include <aidl/android/hardware/security/keymint/MacedPublicKey.h>
+#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
+#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
+
+#include "KeyMintUtils.h"
+#include "guest/hals/keymint/remote/remote_keymaster.h"
+
+namespace aidl::android::hardware::security::keymint {
+
+class RemoteRemotelyProvisionedComponent
+    : public BnRemotelyProvisionedComponent {
+ public:
+  explicit RemoteRemotelyProvisionedComponent(keymaster::RemoteKeymaster& impl);
+
+  ndk::ScopedAStatus getHardwareInfo(RpcHardwareInfo* info) override;
+
+  ndk::ScopedAStatus generateEcdsaP256KeyPair(
+      bool testMode, MacedPublicKey* macedPublicKey,
+      std::vector<uint8_t>* privateKeyHandle) override;
+
+  ndk::ScopedAStatus generateCertificateRequest(
+      bool testMode, const std::vector<MacedPublicKey>& keysToSign,
+      const std::vector<uint8_t>& endpointEncCertChain,
+      const std::vector<uint8_t>& challenge, DeviceInfo* deviceInfo,
+      ProtectedData* protectedData,
+      std::vector<uint8_t>* keysToSignMac) override;
+
+ private:
+  keymaster::RemoteKeymaster& impl_;
+};
+
+}  // namespace aidl::android::hardware::security::keymint
diff --git a/guest/hals/keymint/remote/remote_secure_clock.cpp b/guest/hals/keymint/remote/remote_secure_clock.cpp
index 53bab78..307e0ab 100644
--- a/guest/hals/keymint/remote/remote_secure_clock.cpp
+++ b/guest/hals/keymint/remote/remote_secure_clock.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "android.hardware.security.secureclock-impl"
+#define LOG_TAG "android.hardware.security.secureclock-impl.remote"
 #include <log/log.h>
 
 #include "guest/hals/keymint/remote/remote_secure_clock.h"
diff --git a/guest/hals/keymint/remote/remote_shared_secret.cpp b/guest/hals/keymint/remote/remote_shared_secret.cpp
new file mode 100644
index 0000000..6c50900
--- /dev/null
+++ b/guest/hals/keymint/remote/remote_shared_secret.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "android.hardware.security.sharedsecret-impl"
+#include <log/log.h>
+
+#include "guest/hals/keymint/remote/remote_shared_secret.h"
+
+#include <aidl/android/hardware/security/keymint/ErrorCode.h>
+#include <keymaster/android_keymaster.h>
+#include "KeyMintUtils.h"
+
+namespace aidl::android::hardware::security::sharedsecret {
+
+using namespace ::keymaster;
+using namespace ::aidl::android::hardware::security::keymint::km_utils;
+
+RemoteSharedSecret::RemoteSharedSecret(::keymaster::RemoteKeymaster& keymint)
+    : impl_(keymint) {}
+
+RemoteSharedSecret::~RemoteSharedSecret() {}
+
+ScopedAStatus RemoteSharedSecret::getSharedSecretParameters(
+    SharedSecretParameters* params) {
+  auto response = impl_.GetHmacSharingParameters();
+  params->seed = kmBlob2vector(response.params.seed);
+  params->nonce = {std::begin(response.params.nonce),
+                   std::end(response.params.nonce)};
+  return kmError2ScopedAStatus(response.error);
+}
+
+ScopedAStatus RemoteSharedSecret::computeSharedSecret(
+    const vector<SharedSecretParameters>& params,
+    vector<uint8_t>* sharingCheck) {
+  ComputeSharedHmacRequest request(impl_.message_version());
+  request.params_array.params_array =
+      new keymaster::HmacSharingParameters[params.size()];
+  request.params_array.num_params = params.size();
+  for (size_t i = 0; i < params.size(); ++i) {
+    request.params_array.params_array[i].seed = {params[i].seed.data(),
+                                                 params[i].seed.size()};
+    if (sizeof(request.params_array.params_array[i].nonce) !=
+        params[i].nonce.size()) {
+      return kmError2ScopedAStatus(KM_ERROR_INVALID_ARGUMENT);
+    }
+    memcpy(request.params_array.params_array[i].nonce, params[i].nonce.data(),
+           params[i].nonce.size());
+  }
+  auto response = impl_.ComputeSharedHmac(request);
+  if (response.error == KM_ERROR_OK)
+    *sharingCheck = kmBlob2vector(response.sharing_check);
+  return kmError2ScopedAStatus(response.error);
+}
+
+}  // namespace aidl::android::hardware::security::sharedsecret
diff --git a/guest/hals/keymint/remote/remote_shared_secret.h b/guest/hals/keymint/remote/remote_shared_secret.h
new file mode 100644
index 0000000..9c1aae2
--- /dev/null
+++ b/guest/hals/keymint/remote/remote_shared_secret.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 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 <aidl/android/hardware/security/sharedsecret/BnSharedSecret.h>
+#include <aidl/android/hardware/security/sharedsecret/SharedSecretParameters.h>
+#include "guest/hals/keymint/remote/remote_keymaster.h"
+
+namespace keymaster {
+class AndroidKeymaster;
+}
+namespace aidl::android::hardware::security::sharedsecret {
+using ::ndk::ScopedAStatus;
+using std::shared_ptr;
+using std::vector;
+
+class RemoteSharedSecret : public BnSharedSecret {
+ public:
+  explicit RemoteSharedSecret(::keymaster::RemoteKeymaster& keymint);
+  virtual ~RemoteSharedSecret();
+  ScopedAStatus getSharedSecretParameters(
+      SharedSecretParameters* params) override;
+  ScopedAStatus computeSharedSecret(
+      const vector<SharedSecretParameters>& params,
+      vector<uint8_t>* sharingCheck) override;
+
+ private:
+  ::keymaster::RemoteKeymaster& impl_;
+};
+}  // namespace aidl::android::hardware::security::sharedsecret
diff --git a/guest/hals/keymint/remote/service.cpp b/guest/hals/keymint/remote/service.cpp
index 0591edf..f27b0dc 100644
--- a/guest/hals/keymint/remote/service.cpp
+++ b/guest/hals/keymint/remote/service.cpp
@@ -14,32 +14,43 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "android.hardware.security.keymint-service"
+#define LOG_TAG "android.hardware.security.keymint-service.remote"
 
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
 
+#include <keymaster/android_keymaster_messages.h>
+#include <keymaster/km_version.h>
 #include <keymaster/soft_keymaster_logger.h>
-#include "guest/hals/keymint/remote/remote_keymint_device.h"
 
+#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
 #include <guest/hals/keymint/remote/remote_keymaster.h>
 #include <guest/hals/keymint/remote/remote_keymint_device.h>
+#include <guest/hals/keymint/remote/remote_remotely_provisioned_component.h>
 #include <guest/hals/keymint/remote/remote_secure_clock.h>
+#include <guest/hals/keymint/remote/remote_shared_secret.h>
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/security/keymaster_channel.h"
 
-static const char device[] = "/dev/hvc3";
+namespace {
+
+const char device[] = "/dev/hvc3";
 
 using aidl::android::hardware::security::keymint::RemoteKeyMintDevice;
+using aidl::android::hardware::security::keymint::
+    RemoteRemotelyProvisionedComponent;
 using aidl::android::hardware::security::keymint::SecurityLevel;
 using aidl::android::hardware::security::secureclock::RemoteSecureClock;
+using aidl::android::hardware::security::sharedsecret::RemoteSharedSecret;
+using keymaster::GenerateTimestampTokenRequest;
+using keymaster::GenerateTimestampTokenResponse;
 
 template <typename T, class... Args>
-static std::shared_ptr<T> addService(Args&&... args) {
+std::shared_ptr<T> addService(Args&&... args) {
   std::shared_ptr<T> ser =
       ndk::SharedRefBase::make<T>(std::forward<Args>(args)...);
-  auto instanceName = std::string(T::descriptor) + "/remote";
+  auto instanceName = std::string(T::descriptor) + "/default";
   LOG(INFO) << "adding keymint service instance: " << instanceName;
   binder_status_t status =
       AServiceManager_addService(ser->asBinder().get(), instanceName.c_str());
@@ -47,7 +58,23 @@
   return ser;
 }
 
-int main() {
+SecurityLevel getSecurityLevel(::keymaster::RemoteKeymaster& remote_keymaster) {
+  GenerateTimestampTokenRequest request(remote_keymaster.message_version());
+  GenerateTimestampTokenResponse response(remote_keymaster.message_version());
+  remote_keymaster.GenerateTimestampToken(request, &response);
+
+  if (response.error != STATUS_OK) {
+    LOG(FATAL) << "Error getting timestamp token from remote keymaster: "
+               << response.error;
+  }
+
+  return static_cast<SecurityLevel>(response.token.security_level);
+}
+
+}  // namespace
+
+int main(int, char** argv) {
+  android::base::InitLogging(argv, android::base::KernelLogger);
   // Zero threads seems like a useless pool, but below we'll join this thread to
   // it, increasing the pool size to 1.
   ABinderProcess_setThreadPoolMaxThreadCount(0);
@@ -64,11 +91,19 @@
 
   cuttlefish::KeymasterChannel keymasterChannel(fd, fd);
 
-  keymaster::RemoteKeymaster remote_keymaster(&keymasterChannel);
+  keymaster::RemoteKeymaster remote_keymaster(
+      &keymasterChannel, keymaster::MessageVersion(
+                             keymaster::KmVersion::KEYMINT_1, 0 /* km_date */));
+
+  if (!remote_keymaster.Initialize()) {
+    LOG(FATAL) << "Could not initialize keymaster";
+  }
 
   addService<RemoteKeyMintDevice>(remote_keymaster,
-                                  SecurityLevel::TRUSTED_ENVIRONMENT);
+                                  getSecurityLevel(remote_keymaster));
   addService<RemoteSecureClock>(remote_keymaster);
+  addService<RemoteSharedSecret>(remote_keymaster);
+  addService<RemoteRemotelyProvisionedComponent>(remote_keymaster);
 
   ABinderProcess_joinThreadPool();
   return EXIT_FAILURE;  // should not reach
diff --git a/guest/hals/ril/reference-libril/ril_commands.h b/guest/hals/ril/reference-libril/ril_commands.h
index 81629b0..a100b48 100644
--- a/guest/hals/ril/reference-libril/ril_commands.h
+++ b/guest/hals/ril/reference-libril/ril_commands.h
@@ -40,7 +40,7 @@
     {RIL_REQUEST_RADIO_POWER, radio_1_6::setRadioPowerResponse},
     {RIL_REQUEST_DTMF, radio_1_6::sendDtmfResponse},
     {RIL_REQUEST_SEND_SMS, radio_1_6::sendSmsResponse},
-    {RIL_REQUEST_SEND_SMS_EXPECT_MORE, radio_1_6::sendSMSExpectMoreResponse},
+    {RIL_REQUEST_SEND_SMS_EXPECT_MORE, radio_1_6::sendSmsExpectMoreResponse},
     {RIL_REQUEST_SETUP_DATA_CALL, radio_1_6::setupDataCallResponse},
     {RIL_REQUEST_SIM_IO, radio_1_6::iccIOForAppResponse},
     {RIL_REQUEST_SEND_USSD, radio_1_6::sendUssdResponse},
diff --git a/guest/hals/ril/reference-libril/ril_config.cpp b/guest/hals/ril/reference-libril/ril_config.cpp
index 9da85b6..5a72db9 100644
--- a/guest/hals/ril/reference-libril/ril_config.cpp
+++ b/guest/hals/ril/reference-libril/ril_config.cpp
@@ -23,8 +23,6 @@
 #include <android/hardware/radio/config/1.3/IRadioConfig.h>
 #include <android/hardware/radio/config/1.2/IRadioConfigIndication.h>
 #include <android/hardware/radio/1.1/types.h>
-#include <android/hardware/radio/1.3/types.h>
-
 
 #include <ril.h>
 #include <guest/hals/ril/reference-libril/ril_service.h>
@@ -499,9 +497,15 @@
         ::android::hardware::radio::V1_6::RadioResponseInfo responseInfo = {};
         populateResponseInfo_1_6(responseInfo, serial, responseType, e);
 
-        V1_3::HalDeviceCapabilities halCap = {};
+        bool modemReducedFeatureSet1 = false;
+        if (response == NULL || responseLen != sizeof(bool)) {
+            RLOGE("getHalDeviceCapabilitiesResponse Invalid response.");
+        } else {
+            modemReducedFeatureSet1 = (*((bool *) response));
+        }
+
         Return<void> retStatus = radioConfigService->mRadioConfigResponseV1_3->getHalDeviceCapabilitiesResponse(
-                responseInfo, halCap);
+                responseInfo, modemReducedFeatureSet1);
         radioConfigService->checkReturnStatus_config(retStatus);
     } else {
         RLOGE("getHalDeviceCapabilitiesResponse: radioConfigService->getHalDeviceCapabilities == NULL");
diff --git a/guest/hals/ril/reference-libril/ril_service.cpp b/guest/hals/ril/reference-libril/ril_service.cpp
index 848d4dd..0d18ce8 100644
--- a/guest/hals/ril/reference-libril/ril_service.cpp
+++ b/guest/hals/ril/reference-libril/ril_service.cpp
@@ -624,7 +624,7 @@
             const ::android::hardware::radio::V1_6::OptionalTrafficDescriptor& trafficDescriptor,
             bool matchAllRuleAllowed);
     Return<void> sendSms_1_6(int32_t serial, const GsmSmsMessage& message);
-    Return<void> sendSMSExpectMore_1_6(int32_t serial, const GsmSmsMessage& message);
+    Return<void> sendSmsExpectMore_1_6(int32_t serial, const GsmSmsMessage& message);
     Return<void> sendCdmaSms_1_6(int32_t serial, const CdmaSmsMessage& sms);
     Return<void> sendCdmaSmsExpectMore_1_6(int32_t serial, const CdmaSmsMessage& sms);
     Return<void> setRadioPower_1_6(int32_t serial, bool powerOn, bool forEmergencyCall,
@@ -641,7 +641,7 @@
     Return<void> getSystemSelectionChannels(int32_t serial);
     Return<void> getVoiceRegistrationState_1_6(int32_t serial);
     Return<void> getDataRegistrationState_1_6(int32_t serial);
-    Return<void> getAllowedNetworkTypesBitmap(uint32_t serial);
+    Return<void> getAllowedNetworkTypesBitmap(int32_t serial);
     Return<void> getSlicingConfig(int32_t serial);
     Return<void> setCarrierInfoForImsiEncryption_1_6(
             int32_t serial,
@@ -1332,16 +1332,16 @@
 
 Return<void> RadioImpl_1_6::sendSMSExpectMore(int32_t serial, const GsmSmsMessage& message) {
 #if VDBG
-    RLOGD("sendSMSExpectMore: serial %d", serial);
+    RLOGD("sendSmsExpectMore: serial %d", serial);
 #endif
     dispatchStrings(serial, mSlotId, RIL_REQUEST_SEND_SMS_EXPECT_MORE, false,
             2, message.smscPdu.c_str(), message.pdu.c_str());
     return Void();
 }
 
-Return<void> RadioImpl_1_6::sendSMSExpectMore_1_6(int32_t serial, const GsmSmsMessage& message) {
+Return<void> RadioImpl_1_6::sendSmsExpectMore_1_6(int32_t serial, const GsmSmsMessage& message) {
 #if VDBG
-    RLOGD("sendSMSExpectMore: serial %d", serial);
+    RLOGD("sendSmsExpectMore: serial %d", serial);
 #endif
     dispatchStrings(serial, mSlotId, RIL_REQUEST_SEND_SMS_EXPECT_MORE, false,
             2, message.smscPdu.c_str(), message.pdu.c_str());
@@ -3932,7 +3932,7 @@
     return Void();
 }
 
-Return<void> RadioImpl_1_6::getAllowedNetworkTypesBitmap(uint32_t serial) {
+Return<void> RadioImpl_1_6::getAllowedNetworkTypesBitmap(int32_t serial) {
 #if VDBG
     RLOGD("getAllowedNetworkTypesBitmap: serial %d", serial);
 #endif
@@ -6347,15 +6347,15 @@
                     regResponse.accessTechnologySpecificInfo.cdmaInfo(cdmaInfo);
                 } else if (rat == RADIO_TECH_NR) {
                     // rat is NR only for NR SA
-                    V1_6::RegStateResult::AccessTechnologySpecificInfo::
-                        NgranRegistrationInfo ngranInfo;
-                    ngranInfo.nrVopsInfo.vopsSupported =
+                    V1_6::NrVopsInfo nrVopsInfo;
+                    nrVopsInfo.vopsSupported =
                             ::android::hardware::radio::V1_6::VopsIndicator::VOPS_NOT_SUPPORTED;
-                    ngranInfo.nrVopsInfo.emcSupported =
+                    nrVopsInfo.emcSupported =
                             ::android::hardware::radio::V1_6::EmcIndicator::EMC_NOT_SUPPORTED;
-                    ngranInfo.nrVopsInfo.emfSupported =
+                    nrVopsInfo.emfSupported =
                             ::android::hardware::radio::V1_6::EmfIndicator::EMF_NOT_SUPPORTED;
-                    regResponse.accessTechnologySpecificInfo.ngranInfo(ngranInfo);
+                    regResponse.accessTechnologySpecificInfo.ngranNrVopsInfo(nrVopsInfo);
+
                 } else {
                     V1_5::RegStateResult::AccessTechnologySpecificInfo::
                         EutranRegistrationInfo eutranInfo;
@@ -6524,15 +6524,15 @@
                         numStrings, resp);
                 if (rat == RADIO_TECH_NR) {
                     // rat is NR only for NR SA
-                    V1_6::RegStateResult::AccessTechnologySpecificInfo::
-                        NgranRegistrationInfo ngranInfo;
-                    ngranInfo.nrVopsInfo.vopsSupported =
+                    V1_6::NrVopsInfo nrVopsInfo;
+                    nrVopsInfo.vopsSupported =
                             ::android::hardware::radio::V1_6::VopsIndicator::VOPS_NOT_SUPPORTED;
-                    ngranInfo.nrVopsInfo.emcSupported =
+                    nrVopsInfo.emcSupported =
                             ::android::hardware::radio::V1_6::EmcIndicator::EMC_NOT_SUPPORTED;
-                    ngranInfo.nrVopsInfo.emfSupported =
+                    nrVopsInfo.emfSupported =
                             ::android::hardware::radio::V1_6::EmfIndicator::EMF_NOT_SUPPORTED;
-                    regResponse.accessTechnologySpecificInfo.ngranInfo(ngranInfo);
+                    regResponse.accessTechnologySpecificInfo.ngranNrVopsInfo(nrVopsInfo);
+
                 } else {
                     V1_5::RegStateResult::AccessTechnologySpecificInfo::
                             EutranRegistrationInfo eutranInfo;
@@ -6809,11 +6809,11 @@
     return 0;
 }
 
-int radio_1_6::sendSMSExpectMoreResponse(int slotId,
+int radio_1_6::sendSmsExpectMoreResponse(int slotId,
                                     int responseType, int serial, RIL_Errno e, void *response,
                                     size_t responseLen) {
 #if VDBG
-    RLOGD("sendSMSExpectMoreResponse: serial %d", serial);
+    RLOGD("sendSmsExpectMoreResponse: serial %d", serial);
 #endif
 
     if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
@@ -6822,7 +6822,7 @@
                 responseLen);
 
         Return<void> retStatus = radioService[slotId]->mRadioResponseV1_6
-                ->sendSMSExpectMoreResponse_1_6(responseInfo_1_6, result);
+                ->sendSmsExpectMoreResponse_1_6(responseInfo_1_6, result);
         radioService[slotId]->checkReturnStatus(retStatus);
     } else if (radioService[slotId]->mRadioResponse != NULL) {
         RadioResponseInfo responseInfo = {};
diff --git a/guest/hals/ril/reference-libril/ril_service.h b/guest/hals/ril/reference-libril/ril_service.h
index 30c7b69..6821f2c 100644
--- a/guest/hals/ril/reference-libril/ril_service.h
+++ b/guest/hals/ril/reference-libril/ril_service.h
@@ -117,7 +117,7 @@
                    int responseType, int serial, RIL_Errno e, void *response,
                    size_t responselen);
 
-int sendSMSExpectMoreResponse(int slotId,
+int sendSmsExpectMoreResponse(int slotId,
                              int responseType, int serial, RIL_Errno e, void *response,
                              size_t responselen);
 
diff --git a/guest/hals/ril/reference-ril/reference-ril.c b/guest/hals/ril/reference-ril/reference-ril.c
index c1d049d..51cc3ab 100644
--- a/guest/hals/ril/reference-ril/reference-ril.c
+++ b/guest/hals/ril/reference-ril/reference-ril.c
@@ -63,8 +63,8 @@
 #else
 #define PPP_TTY_PATH_ETH0 "eth0"
 #endif
-// This is used if Wifi is supported to separate radio and wifi interface
-#define PPP_TTY_PATH_RADIO0 "radio0"
+// This is used for emulator
+#define EMULATOR_RADIO_INTERFACE "eth0"
 
 // for sim
 #define AUTH_CONTEXT_EAP_SIM                    128
@@ -542,9 +542,9 @@
 }
 
 #ifdef CUTTLEFISH_ENABLE
-static void set_Ip_Addr(const char *addr) {
+static void set_Ip_Addr(const char *addr, const char* radioInterfaceName) {
   RLOGD("%s %d setting ip addr %s on interface %s", __func__, __LINE__, addr,
-        PPP_TTY_PATH_ETH0);
+        radioInterfaceName);
   struct ifreq request;
   int status = 0;
   int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
@@ -554,7 +554,7 @@
   }
 
   memset(&request, 0, sizeof(request));
-  strncpy(request.ifr_name, PPP_TTY_PATH_ETH0, sizeof(request.ifr_name));
+  strncpy(request.ifr_name, radioInterfaceName, sizeof(request.ifr_name));
   request.ifr_name[sizeof(request.ifr_name) - 1] = '\0';
 
   char *myaddr = strdup(addr);
@@ -780,15 +780,12 @@
     RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
 }
 
-static bool hasWifiCapability()
+static const char* getRadioInterfaceName()
 {
-    char propValue[PROP_VALUE_MAX];
-    return property_get("ro.boot.qemu.wifi", propValue, "") > 0 && strcmp("1", propValue) == 0;
-}
-
-static const char* getRadioInterfaceName(bool hasWifi)
-{
-    return hasWifi ? PPP_TTY_PATH_RADIO0 : PPP_TTY_PATH_ETH0;
+    if (isInEmulator()) {
+        return EMULATOR_RADIO_INTERFACE;
+    }
+    return PPP_TTY_PATH_ETH0;
 }
 
 static void requestOrSendDataCallList(int cid, RIL_Token *t)
@@ -799,8 +796,7 @@
     int n = 0;
     char *out = NULL;
     char propValue[PROP_VALUE_MAX] = {0};
-    bool hasWifi = hasWifiCapability();
-    const char* radioInterfaceName = getRadioInterfaceName(hasWifi);
+    const char* radioInterfaceName = getRadioInterfaceName();
 
     err = at_send_command_multiline ("AT+CGACT?", "+CGACT:", &p_response);
     if (err != 0 || p_response->success == 0) {
@@ -912,7 +908,7 @@
         responses[i].addresses = alloca(addresses_size);
         strlcpy(responses[i].addresses, out, addresses_size);
 #ifdef CUTTLEFISH_ENABLE
-        set_Ip_Addr(responses[i].addresses);
+        set_Ip_Addr(responses[i].addresses, radioInterfaceName);
 #endif
 
         if (isInEmulator()) {
@@ -949,12 +945,7 @@
             }
             responses[i].dnses = dnslist;
 
-            /* There is only one gateway in the emulator. If WiFi is
-             * configured the interface visible to RIL will be behind a NAT
-             * where the gateway is different. */
-            if (hasWifi) {
-                responses[i].gateways = "192.168.200.1";
-            } else if (property_get("vendor.net.eth0.gw", propValue, "") > 0) {
+            if (property_get("vendor.net.eth0.gw", propValue, "") > 0) {
                 responses[i].gateways = propValue;
             } else {
                 responses[i].gateways = "";
@@ -2778,8 +2769,7 @@
         if (qmistatus < 0) goto error;
 
     } else {
-        bool hasWifi = hasWifiCapability();
-        const char* radioInterfaceName = getRadioInterfaceName(hasWifi);
+        const char* radioInterfaceName = getRadioInterfaceName();
         if (setInterfaceState(radioInterfaceName, kInterfaceUp) != RIL_E_SUCCESS) {
             goto error;
         }
@@ -2838,8 +2828,7 @@
         return;
     }
 
-    bool hasWifi = hasWifiCapability();
-    const char* radioInterfaceName = getRadioInterfaceName(hasWifi);
+    const char* radioInterfaceName = getRadioInterfaceName();
     rilErrno = setInterfaceState(radioInterfaceName, kInterfaceDown);
     RIL_onRequestComplete(t, rilErrno, NULL, 0);
     putPDP(cid);
@@ -3240,7 +3229,7 @@
                         s_lac, // lac
                         s_cid, // cid
                         0, //arfcn unknown
-                        0xFF, // bsic unknown
+                        0x1, // Base Station Identity Code set to arbitrarily 1
                     },
                     {  // gsm.signalStrengthGsm
                         10, // signalStrength
@@ -4229,6 +4218,16 @@
     pSimSlotStatus->eid = "";
 }
 
+void sendUnsolNetworkScanResult() {
+    RIL_NetworkScanResult scanr;
+    memset(&scanr, 0, sizeof(scanr));
+    scanr.status = COMPLETE;
+    scanr.error = RIL_E_SUCCESS;
+    scanr.network_infos = NULL;
+    scanr.network_infos_length = 0;
+    RIL_onUnsolicitedResponse(RIL_UNSOL_NETWORK_SCAN_RESULT, &scanr, sizeof(scanr));
+}
+
 void onIccSlotStatus(RIL_Token t) {
     RIL_SimSlotStatus_V1_2 *pSimSlotStatusList =
         (RIL_SimSlotStatus_V1_2 *)calloc(SIM_COUNT, sizeof(RIL_SimSlotStatus_V1_2));
@@ -4847,6 +4846,8 @@
         // New requests after P.
         case RIL_REQUEST_START_NETWORK_SCAN:
             RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+            // send unsol network scan results after a short while
+            RIL_requestTimedCallback (sendUnsolNetworkScanResult, NULL, &TIMEVAL_SIMPOLL);
             break;
         case RIL_REQUEST_GET_MODEM_STACK_STATUS:
             RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
@@ -4913,9 +4914,15 @@
                             phoneCapability, sizeof(RIL_PhoneCapability));
             break;
         }
-        case RIL_REQUEST_CONFIG_SET_MODEM_CONFIG:
-            RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+        case RIL_REQUEST_CONFIG_SET_MODEM_CONFIG: {
+            RIL_ModemConfig *mdConfig = (RIL_ModemConfig*)(data);
+            if (mdConfig == NULL || mdConfig->numOfLiveModems != 1) {
+                RIL_onRequestComplete(t, RIL_E_INVALID_ARGUMENTS, NULL, 0);
+            } else {
+                RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+            }
             break;
+        }
         case RIL_REQUEST_CONFIG_GET_MODEM_CONFIG: {
             RIL_ModemConfig mdConfig;
             mdConfig.numOfLiveModems = 1;
@@ -4923,10 +4930,15 @@
             RIL_onRequestComplete(t, RIL_E_SUCCESS, &mdConfig, sizeof(RIL_ModemConfig));
             break;
         }
-        case RIL_REQUEST_CONFIG_SET_PREFER_DATA_MODEM:
-            RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+        case RIL_REQUEST_CONFIG_SET_PREFER_DATA_MODEM: {
+            int *modemId = (int*)(data);
+            if (modemId == NULL || *modemId != 0) {
+                RIL_onRequestComplete(t, RIL_E_INVALID_ARGUMENTS, NULL, 0);
+            } else {
+                RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+            }
             break;
-
+        }
         case RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA:
             RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
             break;
diff --git a/guest/libs/Android.mk b/guest/libs/Android.mk
new file mode 100644
index 0000000..428f4b5
--- /dev/null
+++ b/guest/libs/Android.mk
@@ -0,0 +1,18 @@
+# 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
new file mode 100644
index 0000000..31179f5
--- /dev/null
+++ b/guest/libs/wpa_supplicant_8_lib/Android.mk
@@ -0,0 +1,82 @@
+# 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/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.c b/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.c
new file mode 100644
index 0000000..7181498
--- /dev/null
+++ b/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.c
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+/*
+ * Driver interaction with extended Linux CFG8021
+ */
+
+#include "driver_cmd_nl80211.h"
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <netinet/in.h>
+#include "driver_nl80211.h"
+
+#include "android_drv.h"
+#include "common.h"
+#include "config.h"
+#include "wpa_supplicant_i.h"
+
+int wpa_driver_nl80211_driver_cmd(void* priv, char* cmd, char* buf,
+                                  size_t buf_len) {
+  struct i802_bss* bss = priv;
+  struct wpa_driver_nl80211_data* drv = bss->drv;
+  struct ifreq ifr;
+  android_wifi_priv_cmd priv_cmd;
+  int ret = 0;
+
+  D("%s: called", __FUNCTION__);
+  if (os_strcasecmp(cmd, "STOP") == 0) {
+    linux_set_iface_flags(drv->global->ioctl_sock, bss->ifname, 0);
+    wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STOPPED");
+  } else if (os_strcasecmp(cmd, "START") == 0) {
+    linux_set_iface_flags(drv->global->ioctl_sock, bss->ifname, 1);
+    wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STARTED");
+  } else if (os_strcasecmp(cmd, "MACADDR") == 0) {
+    u8 macaddr[ETH_ALEN] = {};
+
+    ret = linux_get_ifhwaddr(drv->global->ioctl_sock, bss->ifname, macaddr);
+    if (!ret)
+      ret =
+          os_snprintf(buf, buf_len, "Macaddr = " MACSTR "\n", MAC2STR(macaddr));
+  } else if (os_strcasecmp(cmd, "RELOAD") == 0) {
+    wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED");
+  } else {  // Use private command
+    return 0;
+  }
+  return ret;
+}
+
+int wpa_driver_set_p2p_noa(void* priv, u8 count, int start, int duration) {
+  D("%s: called", __FUNCTION__);
+  return 0;
+}
+
+int wpa_driver_get_p2p_noa(void* priv, u8* buf, size_t len) {
+  D("%s: called", __FUNCTION__);
+  return 0;
+}
+
+int wpa_driver_set_p2p_ps(void* priv, int legacy_ps, int opp_ps, int ctwindow) {
+  D("%s: called", __FUNCTION__);
+  return -1;
+}
+
+int wpa_driver_set_ap_wps_p2p_ie(void* priv, const struct wpabuf* beacon,
+                                 const struct wpabuf* proberesp,
+                                 const struct wpabuf* assocresp) {
+  D("%s: called", __FUNCTION__);
+  return 0;
+}
diff --git a/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.h b/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.h
new file mode 100644
index 0000000..0970a63
--- /dev/null
+++ b/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.h
@@ -0,0 +1,40 @@
+#pragma once
+/*
+ * 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.
+ */
+#include <linux/if_ether.h>
+#include <memory.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "common.h"
+#include "linux_ioctl.h"
+#include "wpa_supplicant_i.h"
+
+#define VSOC_WPA_SUPPLICANT_DEBUG 0
+
+#if VSOC_WPA_SUPPLICANT_DEBUG
+#define D(...) ALOGD(__VA_ARGS__)
+#else
+#define D(...) ((void)0)
+#endif
+
+typedef struct android_wifi_priv_cmd {
+  char* buf;
+  int used_len;
+  int total_len;
+} android_wifi_priv_cmd;
diff --git a/guest/monitoring/cuttlefish_service/Android.bp b/guest/monitoring/cuttlefish_service/Android.bp
index 133d233..d33d0e7 100644
--- a/guest/monitoring/cuttlefish_service/Android.bp
+++ b/guest/monitoring/cuttlefish_service/Android.bp
@@ -20,6 +20,9 @@
     name: "CuttlefishService",
     vendor: true,
     srcs: ["java/**/*.java"],
+    resource_dirs: [
+        "res",
+    ],
     static_libs: ["guava"],
     sdk_version: "28",
     privileged: true,
diff --git a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/BluetoothChecker.java b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/BluetoothChecker.java
index 23d4fec..000a96d 100644
--- a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/BluetoothChecker.java
+++ b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/BluetoothChecker.java
@@ -16,6 +16,8 @@
 package com.android.google.gce.gceservice;
 
 import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.res.Resources;
 import android.util.Log;
 
 /*
@@ -28,8 +30,13 @@
     private final GceFuture<Boolean> mEnabled = new GceFuture<Boolean>("Bluetooth");
 
 
-    public BluetoothChecker() {
+    public BluetoothChecker(Context context) {
         super(LOG_TAG);
+        Resources res = context.getResources();
+        if (!res.getBoolean(R.bool.config_bt_required)) {
+            Log.i(LOG_TAG, "Bluetooth not required, assuming enabled.");
+            mEnabled.set(true);
+        }
     }
 
 
diff --git a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/GceService.java b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/GceService.java
index 80d497a..ef0aa03 100644
--- a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/GceService.java
+++ b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/GceService.java
@@ -51,8 +51,8 @@
     private final JobExecutor mExecutor = new JobExecutor();
     private final EventReporter mEventReporter = new EventReporter();
     private final GceBroadcastReceiver mBroadcastReceiver = new GceBroadcastReceiver();
-    private final BluetoothChecker mBluetoothChecker = new BluetoothChecker();
 
+    private BluetoothChecker mBluetoothChecker = null;
     private ConnectivityChecker mConnChecker;
     private GceWifiManager mWifiManager = null;
     private String mMostRecentAction = null;
@@ -76,6 +76,7 @@
             mWindowManager = getSystemService(WindowManager.class);
             mConnChecker = new ConnectivityChecker(this, mEventReporter);
             mWifiManager = new GceWifiManager(this, mEventReporter, mExecutor);
+            mBluetoothChecker = new BluetoothChecker(this);
 
             mPreviousRotation = getRotation();
             mPreviousScreenBounds = getScreenBounds();
diff --git a/guest/monitoring/cuttlefish_service/res/values/config.xml b/guest/monitoring/cuttlefish_service/res/values/config.xml
new file mode 100644
index 0000000..692a686
--- /dev/null
+++ b/guest/monitoring/cuttlefish_service/res/values/config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <!-- Is BT required for boot complete -->
+    <bool name="config_bt_required">true</bool>
+</resources>
diff --git a/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp b/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp
index 3d08780..b283fef 100644
--- a/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp
+++ b/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp
@@ -123,8 +123,9 @@
     }
 
     if (!log_fd->IsOpen()) {
+      auto error = log_fd->StrError();
       ALOGE("Unable to connect to vsock:%u:%u: %s", FLAGS_cid, FLAGS_port,
-        log_fd->StrError());
+            error.c_str());
     } else if (!ifs.is_open()) {
       ALOGE("%s closed in the middle of readout.", ts_path.c_str());
     } else {
diff --git a/guest/services/wifi/init.wifi.sh b/guest/services/wifi/init.wifi.sh
new file mode 100755
index 0000000..a23f174
--- /dev/null
+++ b/guest/services/wifi/init.wifi.sh
@@ -0,0 +1,7 @@
+#!/vendor/bin/sh
+
+wifi_mac_prefix=`getprop ro.boot.wifi_mac_prefix`
+if [ -n "$wifi_mac_prefix" ]; then
+    /vendor/bin/mac80211_create_radios 2 $wifi_mac_prefix || exit 1
+fi
+
diff --git a/host/commands/assemble_cvd/Android.bp b/host/commands/assemble_cvd/Android.bp
index 4ed4452..4afa9de 100644
--- a/host/commands/assemble_cvd/Android.bp
+++ b/host/commands/assemble_cvd/Android.bp
@@ -25,8 +25,10 @@
         "boot_config.cc",
         "boot_image_utils.cc",
         "clean.cc",
+        "config.cpp",
         "disk_flags.cc",
         "flags.cc",
+        "flag_feature.cpp",
         "misc_info.cc",
         "super_image_mixer.cc",
     ],
@@ -37,6 +39,7 @@
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libbase",
+        "libfruit",
         "libjsoncpp",
         "libnl",
         "libprotobuf-cpp-full",
diff --git a/host/commands/assemble_cvd/assemble_cvd.cc b/host/commands/assemble_cvd/assemble_cvd.cc
index c249473..0eabd42 100644
--- a/host/commands/assemble_cvd/assemble_cvd.cc
+++ b/host/commands/assemble_cvd/assemble_cvd.cc
@@ -23,10 +23,12 @@
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
 #include "common/libs/utils/tee_logging.h"
 #include "host/commands/assemble_cvd/clean.h"
 #include "host/commands/assemble_cvd/disk_flags.h"
 #include "host/commands/assemble_cvd/flags.h"
+#include "host/libs/config/adb_config.h"
 #include "host/libs/config/fetcher_config.h"
 
 using cuttlefish::StringFromEnv;
@@ -51,8 +53,7 @@
 FetcherConfig FindFetcherConfig(const std::vector<std::string>& files) {
   FetcherConfig fetcher_config;
   for (const auto& file : files) {
-    auto expected_pos = file.size() - kFetcherConfigFile.size();
-    if (file.rfind(kFetcherConfigFile) == expected_pos) {
+    if (android::base::EndsWith(file, kFetcherConfigFile)) {
       if (fetcher_config.LoadFromFile(file)) {
         return fetcher_config;
       }
@@ -90,7 +91,9 @@
 }
 
 void ValidateAdbModeFlag(const CuttlefishConfig& config) {
-  auto adb_modes = config.adb_mode();
+  AdbConfig adb_config;
+  CHECK(config.LoadFragment(adb_config)) << "No adb fragment";
+  auto adb_modes = adb_config.adb_mode();
   adb_modes.erase(AdbMode::Unknown);
   if (adb_modes.size() < 1) {
     LOG(INFO) << "ADB not enabled";
@@ -133,11 +136,12 @@
     auto config = InitializeCuttlefishConfiguration(
         FLAGS_instance_dir, FLAGS_modem_simulator_count, kernel_config);
     std::set<std::string> preserving;
-    if (FLAGS_resume && ShouldCreateAllCompositeDisks(config)) {
+    bool create_os_composite_disk = ShouldCreateOsCompositeDisk(config);
+    if (FLAGS_resume && create_os_composite_disk) {
       LOG(INFO) << "Requested resuming a previous session (the default behavior) "
                 << "but the base images have changed under the overlay, making the "
                 << "overlay incompatible. Wiping the overlay files.";
-    } else if (FLAGS_resume && !ShouldCreateAllCompositeDisks(config)) {
+    } else if (FLAGS_resume && !create_os_composite_disk) {
       preserving.insert("overlay.img");
       preserving.insert("os_composite_disk_config.txt");
       preserving.insert("os_composite_gpt_header.img");
@@ -248,7 +252,9 @@
   ExtractKernelParamsFromFetcherConfig(fetcher_config);
 
   KernelConfig kernel_config;
-  CHECK(ParseCommandLineFlags(&argc, &argv, &kernel_config)) << "Failed to parse arguments";
+  auto flags = ArgsToVec(argc - 1, argv + 1);
+  CHECK(ParseCommandLineFlags(flags, &kernel_config))
+      << "Failed to parse arguments";
 
   auto config =
       InitFilesystemAndCreateConfig(std::move(fetcher_config), kernel_config);
diff --git a/host/commands/assemble_cvd/boot_config.cc b/host/commands/assemble_cvd/boot_config.cc
index b433940..33d24f4 100644
--- a/host/commands/assemble_cvd/boot_config.cc
+++ b/host/commands/assemble_cvd/boot_config.cc
@@ -29,6 +29,7 @@
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
 #include "common/libs/utils/subprocess.h"
+#include "host/libs/config/bootconfig_args.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/kernel_args.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
@@ -43,10 +44,10 @@
 namespace {
 
 size_t WriteEnvironment(const CuttlefishConfig& config,
-                        const std::vector<std::string>& kernel_args,
+                        const std::string& kernel_args,
                         const std::string& env_path) {
   std::ostringstream env;
-  env << "bootargs=" << android::base::Join(kernel_args, " ") << '\0';
+  env << "bootargs=" << kernel_args << '\0';
   if (!config.boot_slot().empty()) {
       env << "android_slot_suffix=_" << config.boot_slot() << '\0';
   }
@@ -85,8 +86,29 @@
   auto boot_env_image_path = instance.uboot_env_image_path();
   auto tmp_boot_env_image_path = boot_env_image_path + ".tmp";
   auto uboot_env_path = instance.PerInstancePath("mkenvimg_input");
-  auto kernel_args = KernelCommandLineFromConfig(config);
-  if(!WriteEnvironment(config, kernel_args, uboot_env_path)) {
+  auto kernel_cmdline =
+      android::base::Join(KernelCommandLineFromConfig(config), " ");
+  // If the bootconfig isn't supported in the guest kernel, the bootconfig args
+  // need to be passed in via the uboot env. This won't be an issue for protect
+  // kvm which is running a kernel with bootconfig support.
+  if (!config.bootconfig_supported()) {
+    auto bootconfig_args =
+        android::base::Join(BootconfigArgsFromConfig(config, instance), " ");
+    // "androidboot.hardware" kernel parameter has changed to "hardware" in
+    // bootconfig and needs to be replaced before being used in the kernel
+    // cmdline.
+    bootconfig_args = android::base::StringReplace(
+        bootconfig_args, " hardware=", " androidboot.hardware=", true);
+    // TODO(b/182417593): Until we pass the module parameters through
+    // modules.options, we pass them through bootconfig using
+    // 'kernel.<key>=<value>' But if we don't support bootconfig, we need to
+    // rename them back to the old cmdline version
+    bootconfig_args =
+        android::base::StringReplace(bootconfig_args, " kernel.", " ", true);
+    kernel_cmdline += " ";
+    kernel_cmdline += bootconfig_args;
+  }
+  if (!WriteEnvironment(config, kernel_cmdline, uboot_env_path)) {
     LOG(ERROR) << "Unable to write out plaintext env '" << uboot_env_path << ".'";
     return false;
   }
diff --git a/host/commands/assemble_cvd/boot_image_utils.cc b/host/commands/assemble_cvd/boot_image_utils.cc
index 7b463d1..1483d9e 100644
--- a/host/commands/assemble_cvd/boot_image_utils.cc
+++ b/host/commands/assemble_cvd/boot_image_utils.cc
@@ -71,16 +71,6 @@
   return true;
 }
 
-std::string FindCpio() {
-  for (const auto& path : {"/usr/bin/cpio", "/bin/cpio"}) {
-    if (FileExists(path)) {
-      return path;
-    }
-  }
-  LOG(FATAL) << "Could not find a cpio executable.";
-  return "";
-}
-
 bool UnpackBootImage(const std::string& boot_image_path,
                      const std::string& unpack_dir) {
   auto unpack_path = HostBinaryPath("unpack_bootimg");
@@ -111,30 +101,33 @@
                          const std::string& original_ramdisk_path,
                          const std::string& new_ramdisk_path,
                          const std::string& build_dir) {
-  const auto& cpio_path = FindCpio();
   int success = execute({"/bin/bash", "-c", HostBinaryPath("lz4") + " -c -d -l " +
                         original_ramdisk_path + " > " + original_ramdisk_path + CPIO_EXT});
   CHECK(success == 0) << "Unable to run lz4. Exited with status " << success;
 
-  success = mkdir((build_dir + "/" + TMP_RD_DIR).c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
-  CHECK(success == 0) << "Could not mkdir \"" << TMP_RD_DIR << "\", error was " << strerror(errno);
+  const std::string ramdisk_stage_dir = build_dir + "/" + TMP_RD_DIR;
+  success =
+      mkdir(ramdisk_stage_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+  CHECK(success == 0) << "Could not mkdir \"" << ramdisk_stage_dir
+                      << "\", error was " << strerror(errno);
 
-  success = execute({"/bin/bash", "-c",
-                     "(cd " + build_dir + "/" + TMP_RD_DIR + " && (while " +
-                         cpio_path + " -id ; do :; done) < " +
-                         original_ramdisk_path + CPIO_EXT + ")"});
-  CHECK(success == 0) << "Unable to run cd or cpio. Exited with status " << success;
+  success = execute(
+      {"/bin/bash", "-c",
+       "(cd " + ramdisk_stage_dir + " && while " + HostBinaryPath("toybox") +
+           " cpio -idu; do :; done) < " + original_ramdisk_path + CPIO_EXT});
+  CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
+                      << success;
 
-  success = execute({"/bin/bash", "-c", "rm -rf " + build_dir + "/" + TMP_RD_DIR + "/lib/modules"});
-  CHECK(success == 0) << "Could not rmdir \"lib/modules\" in TMP_RD_DIR. Exited with status "
-                  << success;
+  success = execute({"rm", "-rf", ramdisk_stage_dir + "/lib/modules"});
+  CHECK(success == 0) << "Could not rmdir \"lib/modules\" in TMP_RD_DIR. "
+                      << "Exited with status " << success;
 
   const std::string stripped_ramdisk_path = build_dir + "/" + STRIPPED_RD;
   success = execute({"/bin/bash", "-c",
-                     "(cd " + build_dir + "/" + TMP_RD_DIR + " && find . | " +
-                         cpio_path + " -H newc -o --quiet > " +
-                         stripped_ramdisk_path + CPIO_EXT + ")"});
-  CHECK(success == 0) << "Unable to run cd or cpio. Exited with status " << success;
+                     HostBinaryPath("mkbootfs") + " " + ramdisk_stage_dir +
+                         " > " + stripped_ramdisk_path + CPIO_EXT});
+  CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
+                      << success;
 
   success = execute({"/bin/bash", "-c", HostBinaryPath("lz4") +
                      " -c -l -12 --favor-decSpeed " + stripped_ramdisk_path + CPIO_EXT + " > " +
@@ -242,20 +235,12 @@
                            const std::string& vendor_boot_image_path,
                            const std::string& new_vendor_boot_image_path,
                            const std::string& unpack_dir,
-                           const std::string& repack_dir,
-                           const std::vector<std::string>& bootconfig_args,
                            bool bootconfig_supported) {
   if (UnpackVendorBootImageIfNotUnpacked(vendor_boot_image_path, unpack_dir) ==
       false) {
     return false;
   }
 
-  // TODO(b/173134558)
-  // The vendor boot generation below isn't deterministic. i.e. running the same vendor boot
-  // repack function twice with the same inputs will produce two differing vendor boot images.
-  // This is because the vendor boot ramdisk contains a few symlinks. These symlinks affect the
-  // ramdisk regeneration process and cause differing outputs each time (I still haven't figured
-  // out why).
   std::string ramdisk_path;
   if (new_ramdisk.size()) {
     ramdisk_path = unpack_dir + "/vendor_ramdisk_repacked";
@@ -268,17 +253,9 @@
     ramdisk_path = unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK;
   }
 
-  auto bootconfig_fd = SharedFD::Creat(repack_dir + "/bootconfig", 0666);
-  if (!bootconfig_fd->IsOpen()) {
-    LOG(ERROR) << "Unable to create intermediate bootconfig file: "
-               << bootconfig_fd->StrError();
-    return false;
-  }
-  std::string bootconfig = ReadFile(unpack_dir + "/bootconfig") +
-                           android::base::Join(bootconfig_args, "\n") + "\n";
-  bootconfig_fd->Write(bootconfig.c_str(), bootconfig.size());
-  LOG(DEBUG) << "Bootconfig parameters from vendor boot image and config are "
-             << ReadFile(repack_dir + "/bootconfig");
+  std::string bootconfig = ReadFile(unpack_dir + "/bootconfig");
+  LOG(DEBUG) << "Bootconfig parameters from vendor boot image are "
+             << bootconfig;
   std::string vendor_boot_params = ReadFile(unpack_dir + "/vendor_boot_params");
   auto kernel_cmdline =
       ExtractValue(vendor_boot_params, "vendor command line args: ") +
@@ -286,11 +263,6 @@
            ? ""
            : " " + android::base::StringReplace(bootconfig, "\n", " ", true));
   if (!bootconfig_supported) {
-    // "androidboot.hardware" kernel parameter has changed to "hardware" in
-    // bootconfig and needs to be replaced before being used in the kernel
-    // cmdline.
-    kernel_cmdline = android::base::StringReplace(
-        kernel_cmdline, " hardware=", " androidboot.hardware=", true);
     // TODO(b/182417593): Until we pass the module parameters through
     // modules.options, we pass them through bootconfig using
     // 'kernel.<key>=<value>' But if we don't support bootconfig, we need to
@@ -315,7 +287,7 @@
   repack_cmd.AddParameter(unpack_dir + "/dtb");
   if (bootconfig_supported) {
     repack_cmd.AddParameter("--vendor_bootconfig");
-    repack_cmd.AddParameter(repack_dir + "/bootconfig");
+    repack_cmd.AddParameter(unpack_dir + "/bootconfig");
   }
 
   int success = repack_cmd.Start().Wait();
@@ -336,14 +308,11 @@
 bool RepackVendorBootImageWithEmptyRamdisk(
     const std::string& vendor_boot_image_path,
     const std::string& new_vendor_boot_image_path,
-    const std::string& unpack_dir, const std::string& repack_dir,
-    const std::vector<std::string>& bootconfig_args,
-    bool bootconfig_supported) {
+    const std::string& unpack_dir, bool bootconfig_supported) {
   auto empty_ramdisk_file =
       SharedFD::Creat(unpack_dir + "/empty_ramdisk", 0666);
   return RepackVendorBootImage(
       unpack_dir + "/empty_ramdisk", vendor_boot_image_path,
-      new_vendor_boot_image_path, unpack_dir, repack_dir, bootconfig_args,
-      bootconfig_supported);
+      new_vendor_boot_image_path, unpack_dir, bootconfig_supported);
 }
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/boot_image_utils.h b/host/commands/assemble_cvd/boot_image_utils.h
index fafb514..298fdb4 100644
--- a/host/commands/assemble_cvd/boot_image_utils.h
+++ b/host/commands/assemble_cvd/boot_image_utils.h
@@ -27,12 +27,9 @@
                            const std::string& vendor_boot_image_path,
                            const std::string& new_vendor_boot_image_path,
                            const std::string& unpack_dir,
-                           const std::string& repack_dir,
-                           const std::vector<std::string>& bootconfig_args,
                            bool bootconfig_supported);
 bool RepackVendorBootImageWithEmptyRamdisk(
     const std::string& vendor_boot_image_path,
     const std::string& new_vendor_boot_image_path,
-    const std::string& unpack_dir, const std::string& repack_dir,
-    const std::vector<std::string>& bootconfig_args, bool bootconfig_supported);
+    const std::string& unpack_dir, bool bootconfig_supported);
 }
diff --git a/host/commands/assemble_cvd/config.cpp b/host/commands/assemble_cvd/config.cpp
new file mode 100644
index 0000000..bb8ff38
--- /dev/null
+++ b/host/commands/assemble_cvd/config.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+#include "host/commands/assemble_cvd/config.h"
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <gflags/gflags.h>
+#include <json/json.h>
+#include <fstream>
+#include <set>
+#include <string>
+
+#include "common/libs/utils/files.h"
+#include "host/libs/config/cuttlefish_config.h"
+
+using google::FlagSettingMode::SET_FLAGS_DEFAULT;
+
+DECLARE_string(system_image_dir);
+DEFINE_string(config, "phone",
+              "Config preset name. Will automatically set flag fields "
+              "using the values from this file of presets. See "
+              "device/google/cuttlefish/shared/config/config_*.json "
+              "for possible values.");
+
+namespace cuttlefish {
+namespace {
+
+bool IsFlagSet(const std::string& flag) {
+  return !gflags::GetCommandLineFlagInfoOrDie(flag.c_str()).is_default;
+}
+
+}  // namespace
+
+void SetDefaultFlagsFromConfigPreset() {
+  std::string config_preset = FLAGS_config;  // The name of the preset config.
+  std::string config_file_path;  // The path to the preset config JSON.
+  std::set<std::string> allowed_config_presets;
+  for (const std::string& file :
+       DirectoryContents(DefaultHostArtifactsPath("etc/cvd_config"))) {
+    std::string_view local_file(file);
+    if (android::base::ConsumePrefix(&local_file, "cvd_config_") &&
+        android::base::ConsumeSuffix(&local_file, ".json")) {
+      allowed_config_presets.emplace(local_file);
+    }
+  }
+
+  // If the user specifies a --config name, then use that config
+  // preset option.
+  std::string android_info_path = FLAGS_system_image_dir + "/android-info.txt";
+  if (IsFlagSet("config")) {
+    if (!allowed_config_presets.count(config_preset)) {
+      LOG(FATAL) << "Invalid --config option '" << config_preset
+                 << "'. Valid options: "
+                 << android::base::Join(allowed_config_presets, ",");
+    }
+  } else if (FileExists(android_info_path)) {
+    // Otherwise try to load the correct preset using android-info.txt.
+    std::ifstream ifs(android_info_path);
+    if (ifs.is_open()) {
+      std::string android_info;
+      ifs >> android_info;
+      std::string_view local_android_info(android_info);
+      if (android::base::ConsumePrefix(&local_android_info, "config=")) {
+        config_preset = local_android_info;
+      }
+      if (!allowed_config_presets.count(config_preset)) {
+        LOG(WARNING) << android_info_path
+                     << " contains invalid config preset: '"
+                     << local_android_info << "'. Defaulting to 'phone'.";
+        config_preset = "phone";
+      }
+    }
+  }
+  LOG(INFO) << "Launching CVD using --config='" << config_preset << "'.";
+
+  config_file_path = DefaultHostArtifactsPath("etc/cvd_config/cvd_config_" +
+                                              config_preset + ".json");
+  Json::Value config;
+  Json::CharReaderBuilder builder;
+  std::ifstream ifs(config_file_path);
+  std::string errorMessage;
+  if (!Json::parseFromStream(builder, ifs, &config, &errorMessage)) {
+    LOG(FATAL) << "Could not read config file " << config_file_path << ": "
+               << errorMessage;
+  }
+  for (const std::string& flag : config.getMemberNames()) {
+    std::string value;
+    if (flag == "custom_actions") {
+      Json::StreamWriterBuilder factory;
+      value = Json::writeString(factory, config[flag]);
+    } else {
+      value = config[flag].asString();
+    }
+    if (gflags::SetCommandLineOptionWithMode(flag.c_str(), value.c_str(),
+                                             SET_FLAGS_DEFAULT)
+            .empty()) {
+      LOG(FATAL) << "Error setting flag '" << flag << "'.";
+    }
+  }
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/assemble_cvd/config.h
similarity index 66%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/assemble_cvd/config.h
index 9f25445..ae46bbd 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/assemble_cvd/config.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -13,16 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-#include "common/libs/utils/size_utils.h"
-
-#include <unistd.h>
+#pragma once
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+void SetDefaultFlagsFromConfigPreset();
 
-}  // namespace cuttlefish
+}
diff --git a/host/commands/assemble_cvd/disk_flags.cc b/host/commands/assemble_cvd/disk_flags.cc
index 67e278f..768311e 100644
--- a/host/commands/assemble_cvd/disk_flags.cc
+++ b/host/commands/assemble_cvd/disk_flags.cc
@@ -21,10 +21,13 @@
 #include <fstream>
 
 #include <android-base/logging.h>
+#include <android-base/strings.h>
 #include <gflags/gflags.h>
 
+#include "common/libs/fs/shared_buf.h"
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
+#include "common/libs/utils/size_utils.h"
 #include "common/libs/utils/subprocess.h"
 #include "host/commands/assemble_cvd/boot_config.h"
 #include "host/commands/assemble_cvd/boot_image_utils.h"
@@ -130,8 +133,7 @@
   return true;
 }
 
-std::vector<ImagePartition> os_composite_disk_config(
-    const CuttlefishConfig::InstanceSpecific& instance) {
+std::vector<ImagePartition> os_composite_disk_config() {
   std::vector<ImagePartition> partitions;
   partitions.push_back(ImagePartition {
     .label = "misc",
@@ -145,27 +147,14 @@
     .label = "boot_b",
     .image_file_path = FLAGS_boot_image,
   });
-  // Boot image repacking is not supported on protected VMs. Repacking requires
-  // resigning the image and keys on android hosts aren't trusted.
-  if (!FLAGS_protected_vm) {
-    partitions.push_back(ImagePartition{
-        .label = "vendor_boot_a",
-        .image_file_path = instance.vendor_boot_image_path(),
-    });
-    partitions.push_back(ImagePartition{
-        .label = "vendor_boot_b",
-        .image_file_path = instance.vendor_boot_image_path(),
-    });
-  } else {
-    partitions.push_back(ImagePartition{
-        .label = "vendor_boot_a",
-        .image_file_path = FLAGS_vendor_boot_image,
-    });
-    partitions.push_back(ImagePartition{
-        .label = "vendor_boot_b",
-        .image_file_path = FLAGS_vendor_boot_image,
-    });
-  }
+  partitions.push_back(ImagePartition{
+      .label = "vendor_boot_a",
+      .image_file_path = FLAGS_vendor_boot_image,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "vendor_boot_b",
+      .image_file_path = FLAGS_vendor_boot_image,
+  });
   partitions.push_back(ImagePartition {
     .label = "vbmeta_a",
     .image_file_path = FLAGS_vbmeta_image,
@@ -225,6 +214,10 @@
         .image_file_path = instance.factory_reset_protected_path(),
     });
   }
+  partitions.push_back(ImagePartition{
+      .label = "bootconfig",
+      .image_file_path = instance.persistent_bootconfig_path(),
+  });
   return partitions;
 }
 
@@ -244,31 +237,6 @@
   return ret;
 }
 
-bool ShouldCreateAllCompositeDisks(const CuttlefishConfig& config) {
-  std::chrono::system_clock::time_point youngest_disk_img;
-  for (auto& partition :
-       os_composite_disk_config(config.ForDefaultInstance())) {
-    auto partition_mod_time = FileModificationTime(partition.image_file_path);
-    if (partition_mod_time > youngest_disk_img) {
-      youngest_disk_img = partition_mod_time;
-    }
-  }
-
-  // If the youngest partition img is younger than any composite disk, this fact implies that
-  // the composite disks are all out of date and need to be reinitialized.
-  for (auto& instance : config.Instances()) {
-    if (!FileExists(instance.os_composite_disk_path())) {
-      continue;
-    }
-    if (youngest_disk_img >
-        FileModificationTime(instance.os_composite_disk_path())) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
 bool DoesCompositeMatchCurrentDiskConfig(
     const std::string& prior_disk_config_path,
     const std::vector<ImagePartition>& partitions) {
@@ -309,6 +277,11 @@
   return composite_age < LastUpdatedInputDisk(partitions);
 }
 
+bool ShouldCreateOsCompositeDisk(const CuttlefishConfig& config) {
+  return ShouldCreateCompositeDisk(config.os_composite_disk_path(),
+                                   os_composite_disk_config());
+}
+
 static uint64_t AvailableSpaceAtPath(const std::string& path) {
   struct statvfs vfs;
   if (statvfs(path.c_str(), &vfs) != 0) {
@@ -321,12 +294,11 @@
   return static_cast<uint64_t>(vfs.f_frsize) * vfs.f_bavail;
 }
 
-bool CreateCompositeDisk(const CuttlefishConfig& config,
-                         const CuttlefishConfig::InstanceSpecific& instance) {
-  if (!SharedFD::Open(instance.os_composite_disk_path().c_str(),
+bool CreateOsCompositeDisk(const CuttlefishConfig& config) {
+  if (!SharedFD::Open(config.os_composite_disk_path().c_str(),
                       O_WRONLY | O_CREAT, 0644)
            ->IsOpen()) {
-    LOG(ERROR) << "Could not ensure " << instance.os_composite_disk_path()
+    LOG(ERROR) << "Could not ensure " << config.os_composite_disk_path()
                << " exists";
     return false;
   }
@@ -352,16 +324,15 @@
                  << existing_sizes.disk_size;
     }
     std::string header_path =
-        instance.PerInstancePath("os_composite_gpt_header.img");
+        config.AssemblyPath("os_composite_gpt_header.img");
     std::string footer_path =
-        instance.PerInstancePath("os_composite_gpt_footer.img");
-    CreateCompositeDisk(os_composite_disk_config(instance), header_path,
-                        footer_path, instance.os_composite_disk_path());
+        config.AssemblyPath("os_composite_gpt_footer.img");
+    CreateCompositeDisk(os_composite_disk_config(), header_path, footer_path,
+                        config.os_composite_disk_path());
   } else {
     // If this doesn't fit into the disk, it will fail while aggregating. The
     // aggregator doesn't maintain any sparse attributes.
-    AggregateImage(os_composite_disk_config(instance),
-                   instance.os_composite_disk_path());
+    AggregateImage(os_composite_disk_config(), config.os_composite_disk_path());
   }
   return true;
 }
@@ -407,44 +378,69 @@
                                  google::FlagSettingMode::SET_FLAGS_DEFAULT);
   }
 
-  for (auto instance : config->Instances()) {
+  if (FLAGS_kernel_path.size() || FLAGS_initramfs_path.size()) {
     const std::string new_vendor_boot_image_path =
-        instance.vendor_boot_image_path();
-    const std::vector<std::string> boot_config_vector =
-        BootconfigArgsFromConfig(*config, instance);
-    if (FLAGS_kernel_path.size() || FLAGS_initramfs_path.size()) {
-      // Repack the vendor boot images if kernels and/or ramdisks are passed in.
-      if (FLAGS_initramfs_path.size()) {
-        bool success = RepackVendorBootImage(
-            FLAGS_initramfs_path, FLAGS_vendor_boot_image,
-            new_vendor_boot_image_path, config->assembly_dir(),
-            instance.instance_dir(), boot_config_vector,
-            config->bootconfig_supported());
-        CHECK(success) << "Failed to regenerate the vendor boot image with the "
-                          "new ramdisk";
-      } else {
-        // This control flow implies a kernel with all configs built in.
-        // If it's just the kernel, repack the vendor boot image without a
-        // ramdisk.
-        bool success = RepackVendorBootImageWithEmptyRamdisk(
-            FLAGS_vendor_boot_image, new_vendor_boot_image_path,
-            config->assembly_dir(), instance.instance_dir(), boot_config_vector,
-            config->bootconfig_supported());
-        CHECK(success)
-            << "Failed to regenerate the vendor boot image without a ramdisk";
-      }
-    } else {
-      // Repack the vendor boot image to add the instance specific bootconfig
-      // parameters
+        config->AssemblyPath("vendor_boot_repacked.img");
+    // Repack the vendor boot images if kernels and/or ramdisks are passed in.
+    if (FLAGS_initramfs_path.size()) {
       bool success = RepackVendorBootImage(
-          std::string(), FLAGS_vendor_boot_image, new_vendor_boot_image_path,
-          config->assembly_dir(), instance.instance_dir(), boot_config_vector,
+          FLAGS_initramfs_path, FLAGS_vendor_boot_image,
+          new_vendor_boot_image_path, config->assembly_dir(),
           config->bootconfig_supported());
-      CHECK(success) << "Failed to regenerate the vendor boot image";
+      CHECK(success) << "Failed to regenerate the vendor boot image with the "
+                        "new ramdisk";
+    } else {
+      // This control flow implies a kernel with all configs built in.
+      // If it's just the kernel, repack the vendor boot image without a
+      // ramdisk.
+      bool success = RepackVendorBootImageWithEmptyRamdisk(
+          FLAGS_vendor_boot_image, new_vendor_boot_image_path,
+          config->assembly_dir(), config->bootconfig_supported());
+      CHECK(success)
+          << "Failed to regenerate the vendor boot image without a ramdisk";
     }
+    SetCommandLineOptionWithMode("vendor_boot_image",
+                                 new_vendor_boot_image_path.c_str(),
+                                 google::FlagSettingMode::SET_FLAGS_DEFAULT);
   }
 }
 
+static void GeneratePersistentBootconfig(
+    const CuttlefishConfig* config,
+    const CuttlefishConfig::InstanceSpecific& instance) {
+  const auto bootconfig_path = instance.persistent_bootconfig_path();
+  if (!FileExists(bootconfig_path)) {
+    CreateBlankImage(bootconfig_path, 1 /* mb */, "none");
+  }
+
+  auto bootconfig_fd = SharedFD::Open(bootconfig_path, O_RDWR);
+  CHECK(bootconfig_fd->IsOpen())
+      << "Unable to open bootconfig file: " << bootconfig_fd->StrError();
+
+  //  Cuttlefish for the time being won't be able to support OTA from a
+  //  non-bootconfig kernel to a bootconfig-kernel (or vice versa) IF the device
+  //  is stopped (via stop_cvd). This is rarely an issue since OTA testing run
+  //  on cuttlefish is done within one launch cycle of the device. If this ever
+  //  becomes an issue, this code will have to be rewritten.
+  if (!config->bootconfig_supported()) {
+    return;
+  }
+
+  const std::string bootconfig =
+      android::base::Join(BootconfigArgsFromConfig(*config, instance), "\n") +
+      "\n";
+  ssize_t bytesWritten = WriteAll(bootconfig_fd, bootconfig);
+  CHECK(bytesWritten == bootconfig.size());
+  LOG(DEBUG) << "Bootconfig parameters from vendor boot image and config are "
+             << ReadFile(bootconfig_path);
+
+  const off_t bootconfig_size_bytes =
+      AlignToPowerOf2(bootconfig.size(), PARTITION_SIZE_SHIFT);
+  CHECK(bootconfig_fd->Truncate(bootconfig_size_bytes) == 0)
+      << "`truncate --size=" << bootconfig_size_bytes << " bytes "
+      << bootconfig_path << "` failed:" << bootconfig_fd->StrError();
+}
+
 void CreateDynamicDiskFiles(const FetcherConfig& fetcher_config,
                             const CuttlefishConfig* config) {
   // Create misc if necessary
@@ -474,9 +470,10 @@
     RepackAllBootImages(config);
 
     for (const auto& instance : config->Instances()) {
-      if (!FileExists(instance.access_kregistry_path())) {
-        CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none");
-      }
+      // TODO(162770965) Re-enable once QEMU on GCE supports virtio pci pmem devices
+      // if (!FileExists(instance.access_kregistry_path())) {
+      //  CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none");
+      // }
 
       if (!FileExists(instance.pstore_path())) {
         CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none");
@@ -494,6 +491,8 @@
       if (!FileExists(frp)) {
         CreateBlankImage(frp, 1 /* mb */, "none");
       }
+
+      GeneratePersistentBootconfig(config, instance);
     }
   }
 
@@ -530,26 +529,27 @@
     CHECK(success) << "Super image rebuilding requested but could not be completed.";
   }
 
+  bool oldOsCompositeDisk = ShouldCreateOsCompositeDisk(*config);
   bool newDataImage = dataImageResult == DataImageResult::FileUpdated;
+  bool osCompositeMatchesDiskConfig = DoesCompositeMatchCurrentDiskConfig(
+      config->AssemblyPath("os_composite_disk_config.txt"),
+      os_composite_disk_config());
+  if (!osCompositeMatchesDiskConfig || oldOsCompositeDisk || !FLAGS_resume ||
+      newDataImage) {
+    CHECK(CreateOsCompositeDisk(*config))
+        << "Failed to create OS composite disk";
 
-  for (auto instance : config->Instances()) {
-    bool compositeMatchesDiskConfig = DoesCompositeMatchCurrentDiskConfig(
-        instance.PerInstancePath("os_composite_disk_config.txt"),
-        os_composite_disk_config(instance));
-    bool oldCompositeDisk = ShouldCreateCompositeDisk(
-        instance.os_composite_disk_path(), os_composite_disk_config(instance));
-    if (!compositeMatchesDiskConfig || oldCompositeDisk || !FLAGS_resume || newDataImage) {
+    for (auto instance : config->Instances()) {
       if (FLAGS_resume) {
         LOG(INFO) << "Requested to continue an existing session, (the default) "
                   << "but the disk files have become out of date. Wiping the "
                   << "old session files and starting a new session for device "
                   << instance.serial_number();
       }
-      CHECK(CreateCompositeDisk(*config, instance))
-          << "Failed to create composite disk";
-      if (FileExists(instance.access_kregistry_path())) {
-        CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none");
-      }
+      // TODO(162770965) Re-enable once QEMU on GCE supports virtio pci pmem devices
+      // if (FileExists(instance.access_kregistry_path())) {
+      //   CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none");
+      // }
       if (FileExists(instance.pstore_path())) {
         CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none");
       }
@@ -561,10 +561,10 @@
       auto overlay_path = instance.PerInstancePath("overlay.img");
       bool missingOverlay = !FileExists(overlay_path);
       bool newOverlay = FileModificationTime(overlay_path) <
-                        FileModificationTime(instance.os_composite_disk_path());
+                        FileModificationTime(config->os_composite_disk_path());
       if (missingOverlay || !FLAGS_resume || newOverlay) {
         CreateQcowOverlay(config->crosvm_binary(),
-                          instance.os_composite_disk_path(), overlay_path);
+                          config->os_composite_disk_path(), overlay_path);
       }
     }
   }
diff --git a/host/commands/assemble_cvd/disk_flags.h b/host/commands/assemble_cvd/disk_flags.h
index 3491c3a..1fc2d32 100644
--- a/host/commands/assemble_cvd/disk_flags.h
+++ b/host/commands/assemble_cvd/disk_flags.h
@@ -26,7 +26,7 @@
 namespace cuttlefish {
 
 bool ResolveInstanceFiles();
-bool ShouldCreateAllCompositeDisks(const CuttlefishConfig& config);
+bool ShouldCreateOsCompositeDisk(const CuttlefishConfig& config);
 void CreateDynamicDiskFiles(const FetcherConfig& fetcher_config,
                             const CuttlefishConfig* config);
 
diff --git a/host/commands/assemble_cvd/flag_feature.cpp b/host/commands/assemble_cvd/flag_feature.cpp
new file mode 100644
index 0000000..f84a1af
--- /dev/null
+++ b/host/commands/assemble_cvd/flag_feature.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+#include "host/commands/assemble_cvd/flag_feature.h"
+
+#include <android-base/strings.h>
+#include <fruit/fruit.h>
+#include <gflags/gflags.h>
+#include <string.h>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "host/libs/config/feature.h"
+
+namespace cuttlefish {
+
+static std::string XmlEscape(const std::string& s) {
+  using android::base::StringReplace;
+  return StringReplace(StringReplace(s, "<", "&lt;", true), ">", "&gt;", true);
+}
+
+class ParseGflagsImpl : public ParseGflags {
+ public:
+  INJECT(ParseGflagsImpl()) {}
+
+  std::string Name() const override { return "ParseGflags"; }
+  std::unordered_set<FlagFeature*> Dependencies() const override { return {}; }
+  bool Process(std::vector<std::string>& args) override {
+    std::string process_name = "assemble_cvd";
+    std::vector<char*> pseudo_argv = {process_name.data()};
+    for (auto& arg : args) {
+      pseudo_argv.push_back(arg.data());
+    }
+    int argc = pseudo_argv.size();
+    auto argv = pseudo_argv.data();
+    gflags::AllowCommandLineReparsing();  // Support future non-gflags flags
+    gflags::ParseCommandLineNonHelpFlags(&argc, &argv,
+                                         /* remove_flags */ false);
+    return true;
+  }
+  bool WriteGflagsCompatHelpXml(std::ostream& out) const override {
+    // Lifted from external/gflags/src/gflags_reporting.cc:ShowXMLOfFlags
+    std::vector<gflags::CommandLineFlagInfo> flags;
+    gflags::GetAllFlags(&flags);
+    for (const auto& flag : flags) {
+      // From external/gflags/src/gflags_reporting.cc:DescribeOneFlagInXML
+      out << "<flag>\n";
+      out << "  <file>" << XmlEscape(flag.filename) << "</file>\n";
+      out << "  <name>" << XmlEscape(flag.name) << "</name>\n";
+      out << "  <meaning>" << XmlEscape(flag.description) << "</meaning>\n";
+      out << "  <default>" << XmlEscape(flag.default_value) << "</default>\n";
+      out << "  <current>" << XmlEscape(flag.current_value) << "</current>\n";
+      out << "  <type>" << XmlEscape(flag.type) << "</type>\n";
+      out << "</flag>\n";
+    }
+    return true;
+  }
+};
+
+fruit::Component<ParseGflags> GflagsComponent() {
+  return fruit::createComponent()
+      .bind<ParseGflags, ParseGflagsImpl>()
+      .addMultibinding<FlagFeature, ParseGflags>();
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/assemble_cvd/flag_feature.h
similarity index 68%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/assemble_cvd/flag_feature.h
index 9f25445..ab37e6c 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/assemble_cvd/flag_feature.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -13,16 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#pragma once
 
-#include "common/libs/utils/size_utils.h"
+#include <fruit/fruit.h>
 
-#include <unistd.h>
+#include "host/commands/assemble_cvd/config.h"
+#include "host/libs/config/feature.h"
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+class ParseGflags : public FlagFeature {};
+
+fruit::Component<ParseGflags> GflagsComponent();
 
 }  // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index 2d3f9da..2e1e979 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -1,6 +1,7 @@
 #include "host/commands/assemble_cvd/flags.h"
 
 #include <android-base/logging.h>
+#include <android-base/parseint.h>
 #include <android-base/strings.h>
 #include <gflags/gflags.h>
 #include <json/json.h>
@@ -16,13 +17,18 @@
 #include <regex>
 #include <set>
 #include <sstream>
+#include <unordered_map>
 
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
 #include "host/commands/assemble_cvd/alloc.h"
 #include "host/commands/assemble_cvd/boot_config.h"
 #include "host/commands/assemble_cvd/clean.h"
+#include "host/commands/assemble_cvd/config.h"
 #include "host/commands/assemble_cvd/disk_flags.h"
+#include "host/commands/assemble_cvd/flag_feature.h"
+#include "host/libs/config/adb_config.h"
 #include "host/libs/config/host_tools_version.h"
 #include "host/libs/graphics_detector/graphics_detector.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
@@ -35,12 +41,6 @@
 using cuttlefish::vm_manager::CrosvmManager;
 using google::FlagSettingMode::SET_FLAGS_DEFAULT;
 
-DEFINE_string(config, "phone",
-              "Config preset name. Will automatically set flag fields "
-              "using the values from this file of presets. See "
-              "device/google/cuttlefish/shared/config/config_*.json "
-              "for possible values.");
-
 DEFINE_int32(cpus, 2, "Virtual CPU count.");
 DEFINE_string(data_policy, "use_existing", "How to handle userdata partition."
             " Either 'use_existing', 'create_if_missing', 'resize_up_to', or "
@@ -54,6 +54,26 @@
              "kernel must have been built with CONFIG_RANDOMIZE_BASE "
              "disabled.");
 
+constexpr const char kDisplayHelp[] =
+    "Comma separated key=value pairs of display properties. Supported "
+    "properties:\n"
+    " 'width': required, width of the display in pixels\n"
+    " 'height': required, height of the display in pixels\n"
+    " 'dpi': optional, default 320, density of the display\n"
+    " 'refresh_rate_hz': optional, default 60, display refresh rate in Hertz\n"
+    ". Example usage: \n"
+    "--display0=width=1280,height=720\n"
+    "--display1=width=1440,height=900,dpi=480,refresh_rate_hz=30\n";
+
+// TODO(b/192495477): combine these into a single repeatable '--display' flag
+// when assemble_cvd switches to using the new flag parsing library.
+DEFINE_string(display0, "", kDisplayHelp);
+DEFINE_string(display1, "", kDisplayHelp);
+DEFINE_string(display2, "", kDisplayHelp);
+DEFINE_string(display3, "", kDisplayHelp);
+
+// TODO(b/171305898): mark these as deprecated after multi-display is fully
+// enabled.
 DEFINE_int32(x_res, 0, "Width of the screen in pixels");
 DEFINE_int32(y_res, 0, "Height of the screen in pixels");
 DEFINE_int32(dpi, 0, "Pixels per inch for the screen");
@@ -85,6 +105,7 @@
                                      "The VNC server runs at port 6443 + i for "
                                      "the vsoc-i user or CUTTLEFISH_INSTANCE=i, "
                                      "starting from 1.");
+
 DEFINE_bool(use_allocd, false,
             "Acquire static resources from the resource allocator daemon.");
 DEFINE_bool(enable_minimal_mode, false,
@@ -180,9 +201,13 @@
               "The path section of the URL where the device should be "
               "registered with the signaling server.");
 
+DEFINE_bool(webrtc_sig_server_secure, true,
+            "Whether the WebRTC signaling server uses secure protocols (WSS vs WS).");
+
 DEFINE_bool(verify_sig_server_certificate, false,
             "Whether to verify the signaling server's certificate with a "
-            "trusted signing authority (Disallow self signed certificates).");
+            "trusted signing authority (Disallow self signed certificates). "
+            "This is ignored if an insecure server is configured.");
 
 DEFINE_string(sig_server_headers_file, "",
               "Path to a file containing HTTP headers to be included in the "
@@ -265,6 +290,11 @@
 
 DEFINE_bool(vhost_net, false, "Enable vhost acceleration of networking");
 
+DEFINE_string(vhost_user_mac80211_hwsim, "",
+              "Unix socket path for vhost-user of mac80211_hwsim");
+DEFINE_string(ap_rootfs_image, "", "rootfs image for AP instance");
+DEFINE_string(ap_kernel_image, "", "kernel image for AP instance");
+
 DEFINE_bool(record_screen, false, "Enable screen recording. "
                                   "Requires --start_webrtc");
 
@@ -299,6 +329,8 @@
 DEFINE_bool(enable_audio, cuttlefish::HostArch() != cuttlefish::Arch::Arm64,
             "Whether to play or capture audio");
 
+DEFINE_uint32(camera_server_port, 0, "camera vsock port");
+
 DECLARE_string(assembly_dir);
 DECLARE_string(boot_image);
 DECLARE_string(system_image_dir);
@@ -309,10 +341,6 @@
 
 namespace {
 
-bool IsFlagSet(const std::string& flag) {
-  return !gflags::GetCommandLineFlagInfoOrDie(flag.c_str()).is_default;
-}
-
 std::pair<uint16_t, uint16_t> ParsePortRange(const std::string& flag) {
   static const std::regex rgx("[0-9]+:[0-9]+");
   CHECK(std::regex_match(flag, rgx))
@@ -337,6 +365,60 @@
   return stream.str();
 }
 
+std::optional<CuttlefishConfig::DisplayConfig> ParseDisplayConfig(
+    const std::string& flag) {
+  if (flag.empty()) {
+    return std::nullopt;
+  }
+
+  std::unordered_map<std::string, std::string> props;
+
+  const std::vector<std::string> pairs = android::base::Split(flag, ",");
+  for (const std::string& pair : pairs) {
+    const std::vector<std::string> keyvalue = android::base::Split(pair, "=");
+    CHECK_EQ(2, keyvalue.size()) << "Invalid display: " << flag;
+
+    const std::string& prop_key = keyvalue[0];
+    const std::string& prop_val = keyvalue[1];
+    props[prop_key] = prop_val;
+  }
+
+  CHECK(props.find("width") != props.end())
+      << "Display configuration missing 'width' in " << flag;
+  CHECK(props.find("height") != props.end())
+      << "Display configuration missing 'height' in " << flag;
+
+  int display_width;
+  CHECK(android::base::ParseInt(props["width"], &display_width))
+      << "Display configuration invalid 'width' in " << flag;
+
+  int display_height;
+  CHECK(android::base::ParseInt(props["height"], &display_height))
+      << "Display configuration invalid 'height' in " << flag;
+
+  int display_dpi = 320;
+  auto display_dpi_it = props.find("dpi");
+  if (display_dpi_it != props.end()) {
+    CHECK(android::base::ParseInt(display_dpi_it->second, &display_dpi))
+        << "Display configuration invalid 'dpi' in " << flag;
+  }
+
+  int display_refresh_rate_hz = 60;
+  auto display_refresh_rate_hz_it = props.find("refresh_rate_hz");
+  if (display_refresh_rate_hz_it != props.end()) {
+    CHECK(android::base::ParseInt(display_refresh_rate_hz_it->second,
+                                  &display_refresh_rate_hz))
+        << "Display configuration invalid 'refresh_rate_hz' in " << flag;
+  }
+
+  return CuttlefishConfig::DisplayConfig{
+      .width = display_width,
+      .height = display_height,
+      .dpi = display_dpi,
+      .refresh_rate_hz = display_refresh_rate_hz,
+  };
+}
+
 #ifdef __ANDROID__
 void ReadKernelConfig(KernelConfig* kernel_config) {
   // QEMU isn't on Android, so always follow host arch
@@ -408,6 +490,40 @@
   }
   tmp_config_obj.set_vm_manager(FLAGS_vm_manager);
 
+  std::vector<CuttlefishConfig::DisplayConfig> display_configs;
+
+  auto display0 = ParseDisplayConfig(FLAGS_display0);
+  if (display0) {
+    display_configs.push_back(*display0);
+  }
+  auto display1 = ParseDisplayConfig(FLAGS_display1);
+  if (display1) {
+    display_configs.push_back(*display1);
+  }
+  auto display2 = ParseDisplayConfig(FLAGS_display2);
+  if (display2) {
+    display_configs.push_back(*display2);
+  }
+  auto display3 = ParseDisplayConfig(FLAGS_display3);
+  if (display3) {
+    display_configs.push_back(*display3);
+  }
+
+  if (FLAGS_x_res > 0 && FLAGS_y_res > 0) {
+    if (display_configs.empty()) {
+      display_configs.push_back({
+          .width = FLAGS_x_res,
+          .height = FLAGS_y_res,
+          .dpi = FLAGS_dpi,
+          .refresh_rate_hz = FLAGS_refresh_rate_hz,
+      });
+    } else {
+      LOG(WARNING) << "Ignoring --x_res and --y_res when --displayN specified.";
+    }
+  }
+
+  tmp_config_obj.set_display_configs(display_configs);
+
   const GraphicsAvailability graphics_availability =
     GetGraphicsAvailabilityWithSubprocessCheck();
 
@@ -423,8 +539,8 @@
   }
   if (tmp_config_obj.gpu_mode() == kGpuModeAuto) {
     if (ShouldEnableAcceleratedRendering(graphics_availability)) {
-        LOG(INFO) << "GPU auto mode: detected prerequisites for accelerated "
-                     "rendering support.";
+      LOG(INFO) << "GPU auto mode: detected prerequisites for accelerated "
+                   "rendering support.";
       if (FLAGS_vm_manager == QemuManager::name()) {
         LOG(INFO) << "Enabling --gpu_mode=drm_virgl.";
         tmp_config_obj.set_gpu_mode(kGpuModeDrmVirgl);
@@ -469,22 +585,19 @@
 
   tmp_config_obj.set_setupwizard_mode(FLAGS_setupwizard_mode);
 
-  std::vector<cuttlefish::CuttlefishConfig::DisplayConfig> display_configs = {{
-    .width = FLAGS_x_res,
-    .height = FLAGS_y_res,
-  }};
-  tmp_config_obj.set_display_configs(display_configs);
-  tmp_config_obj.set_dpi(FLAGS_dpi);
-  tmp_config_obj.set_refresh_rate_hz(FLAGS_refresh_rate_hz);
-
   auto secure_hals = android::base::Split(FLAGS_secure_hals, ",");
   tmp_config_obj.set_secure_hals(
       std::set<std::string>(secure_hals.begin(), secure_hals.end()));
 
   tmp_config_obj.set_gdb_port(FLAGS_gdb_port);
 
-  std::vector<std::string> adb = android::base::Split(FLAGS_adb_mode, ",");
-  tmp_config_obj.set_adb_mode(std::set<std::string>(adb.begin(), adb.end()));
+  {
+    AdbConfig adb_config;
+    std::vector<std::string> adb = android::base::Split(FLAGS_adb_mode, ",");
+    adb_config.set_adb_mode(std::set<std::string>(adb.begin(), adb.end()));
+    adb_config.set_run_adb_connector(FLAGS_run_adb_connector);
+    CHECK(tmp_config_obj.SaveFragment(adb_config)) << "Failed to save adb info";
+  }
 
   tmp_config_obj.set_guest_enforce_security(FLAGS_guest_enforce_security);
   tmp_config_obj.set_guest_audit_security(FLAGS_guest_audit_security);
@@ -512,6 +625,7 @@
   tmp_config_obj.set_enable_webrtc(FLAGS_start_webrtc);
   tmp_config_obj.set_webrtc_assets_dir(FLAGS_webrtc_assets_dir);
   tmp_config_obj.set_webrtc_certs_dir(FLAGS_webrtc_certs_dir);
+  tmp_config_obj.set_sig_server_secure(FLAGS_webrtc_sig_server_secure);
   // Note: This will be overridden if the sig server is started by us
   tmp_config_obj.set_sig_server_port(FLAGS_webrtc_sig_server_port);
   tmp_config_obj.set_sig_server_address(FLAGS_webrtc_sig_server_addr);
@@ -533,7 +647,6 @@
           FLAGS_webrtc_enable_adb_websocket);
 
   tmp_config_obj.set_restart_subprocesses(FLAGS_restart_subprocesses);
-  tmp_config_obj.set_run_adb_connector(FLAGS_run_adb_connector);
   tmp_config_obj.set_run_as_daemon(FLAGS_daemon);
 
   tmp_config_obj.set_data_policy(FLAGS_data_policy);
@@ -542,9 +655,8 @@
 
   tmp_config_obj.set_enable_gnss_grpc_proxy(FLAGS_start_gnss_proxy);
 
-  tmp_config_obj.set_enable_vehicle_hal_grpc_server(FLAGS_enable_vehicle_hal_grpc_server);
-  tmp_config_obj.set_vehicle_hal_grpc_server_binary(
-      HostBinaryPath("android.hardware.automotive.vehicle@2.0-virtualization-grpc-server"));
+  tmp_config_obj.set_enable_vehicle_hal_grpc_server(
+      FLAGS_enable_vehicle_hal_grpc_server);
 
   std::string custom_action_config;
   if (!FLAGS_custom_action_config.empty()) {
@@ -614,6 +726,20 @@
 
   tmp_config_obj.set_vhost_net(FLAGS_vhost_net);
 
+  tmp_config_obj.set_vhost_user_mac80211_hwsim(FLAGS_vhost_user_mac80211_hwsim);
+
+  if ((FLAGS_ap_rootfs_image.empty()) != (FLAGS_ap_kernel_image.empty())) {
+    LOG(FATAL) << "Either both ap_rootfs_image and ap_kernel_image should be "
+                  "set or neither should be set.";
+  } else if (FLAGS_vhost_user_mac80211_hwsim.empty() &&
+             !FLAGS_ap_rootfs_image.empty() && !FLAGS_ap_kernel_image.empty()) {
+    LOG(FATAL) << "To use external AP instance, vhost_user_mac80211_hwsim must "
+                  "be set.";
+  }
+
+  tmp_config_obj.set_ap_rootfs_image(FLAGS_ap_rootfs_image);
+  tmp_config_obj.set_ap_kernel_image(FLAGS_ap_kernel_image);
+
   tmp_config_obj.set_record_screen(FLAGS_record_screen);
 
   tmp_config_obj.set_ethernet(FLAGS_ethernet);
@@ -670,9 +796,11 @@
 
     instance.set_uuid(FLAGS_uuid);
 
+    instance.set_modem_simulator_host_id(1000 + num);  // Must be 4 digits
     instance.set_vnc_server_port(6444 + num - 1);
-    instance.set_host_port(6520 + num - 1);
+    instance.set_adb_host_port(6520 + num - 1);
     instance.set_adb_ip_and_port("0.0.0.0:" + std::to_string(6520 + num - 1));
+    instance.set_confui_host_vsock_port(7700 + num - 1);
     instance.set_tombstone_receiver_port(calc_vsock_port(6600));
     instance.set_vehicle_hal_server_port(9210 + num - 1);
     instance.set_audiocontrol_server_port(9410);  /* OK to use the same port number across instances */
@@ -701,6 +829,7 @@
     instance.set_rootcanal_default_commands_file(
         FLAGS_bluetooth_default_commands_file);
 
+    instance.set_camera_server_port(FLAGS_camera_server_port);
     instance.set_device_title(FLAGS_device_title);
 
     if (FLAGS_protected_vm) {
@@ -717,14 +846,7 @@
       instance.set_virtual_disk_paths(virtual_disk_paths);
     }
 
-    std::array<unsigned char, 6> mac_address;
-    mac_address[0] = 1 << 6; // locally administered
-    // TODO(schuffelen): Randomize these and preserve the state.
-    for (int i = 1; i < 5; i++) {
-      mac_address[i] = i;
-    }
-    mac_address[5] = num;
-    instance.set_wifi_mac_address(mac_address);
+    instance.set_wifi_mac_prefix(5554 + (num - 1) * 2);
 
     instance.set_start_webrtc_signaling_server(false);
 
@@ -753,9 +875,12 @@
     if (modem_simulator_count > 0) {
       std::stringstream modem_ports;
       for (auto index {0}; index < modem_simulator_count - 1; index++) {
-        modem_ports << calc_vsock_port(9200) << ",";
+        auto port = 9200 + (modem_simulator_count * (num - 1)) + index;
+        modem_ports << calc_vsock_port(port) << ",";
       }
-      modem_ports << calc_vsock_port(9200);
+      auto port = 9200 + (modem_simulator_count * (num - 1)) +
+                  modem_simulator_count - 1;
+      modem_ports << calc_vsock_port(port);
       instance.set_modem_simulator_ports(modem_ports.str());
     } else {
       instance.set_modem_simulator_ports("");
@@ -776,75 +901,7 @@
   return tmp_config_obj;
 }
 
-void SetDefaultFlagsFromConfigPreset() {
-  std::string config_preset = FLAGS_config;  // The name of the preset config.
-  std::string config_file_path;  // The path to the preset config JSON.
-  std::set<std::string> allowed_config_presets;
-  for (const std::string& file :
-       DirectoryContents(DefaultHostArtifactsPath("etc/cvd_config"))) {
-    std::string_view local_file(file);
-    if (android::base::ConsumePrefix(&local_file, "cvd_config_") &&
-        android::base::ConsumeSuffix(&local_file, ".json")) {
-      allowed_config_presets.emplace(local_file);
-    }
-  }
-
-  // If the user specifies a --config name, then use that config
-  // preset option.
-  std::string android_info_path = FLAGS_system_image_dir + "/android-info.txt";
-  if (IsFlagSet("config")) {
-    if (!allowed_config_presets.count(config_preset)) {
-      LOG(FATAL) << "Invalid --config option '" << config_preset
-                 << "'. Valid options: "
-                 << android::base::Join(allowed_config_presets, ",");
-    }
-  } else if (FileExists(android_info_path)) {
-    // Otherwise try to load the correct preset using android-info.txt.
-    std::ifstream ifs(android_info_path);
-    if (ifs.is_open()) {
-      std::string android_info;
-      ifs >> android_info;
-      std::string_view local_android_info(android_info);
-      if (android::base::ConsumePrefix(&local_android_info, "config=")) {
-        config_preset = local_android_info;
-      }
-      if (!allowed_config_presets.count(config_preset)) {
-        LOG(WARNING) << android_info_path
-                     << " contains invalid config preset: '"
-                     << local_android_info << "'. Defaulting to 'phone'.";
-        config_preset = "phone";
-      }
-    }
-  }
-  LOG(INFO) << "Launching CVD using --config='" << config_preset << "'.";
-
-  config_file_path = DefaultHostArtifactsPath("etc/cvd_config/cvd_config_" +
-                                              config_preset + ".json");
-  Json::Value config;
-  Json::CharReaderBuilder builder;
-  std::ifstream ifs(config_file_path);
-  std::string errorMessage;
-  if (!Json::parseFromStream(builder, ifs, &config, &errorMessage)) {
-    LOG(FATAL) << "Could not read config file " << config_file_path << ": "
-               << errorMessage;
-  }
-  for (const std::string& flag : config.getMemberNames()) {
-    std::string value;
-    if (flag == "custom_actions") {
-      Json::StreamWriterBuilder factory;
-      value = Json::writeString(factory, config[flag]);
-    } else {
-      value = config[flag].asString();
-    }
-    if (gflags::SetCommandLineOptionWithMode(flag.c_str(), value.c_str(),
-                                             SET_FLAGS_DEFAULT)
-            .empty()) {
-      LOG(FATAL) << "Error setting flag '" << flag << "'.";
-    }
-  }
-}
-
-void SetDefaultFlagsForQemu() {
+void SetDefaultFlagsForQemu(Arch target_arch) {
   // for now, we don't set non-default options for QEMU
   if (FLAGS_gpu_mode == kGpuModeGuestSwiftshader && NumStreamers() == 0) {
     // This makes WebRTC the default streamer unless the user requests
@@ -852,7 +909,16 @@
     // possible to run without any streamer by setting --start_webrtc=false.
     SetCommandLineOptionWithMode("start_webrtc", "true", SET_FLAGS_DEFAULT);
   }
-  std::string default_bootloader = FLAGS_system_image_dir + "/bootloader.qemu";
+  std::string default_bootloader =
+      DefaultHostArtifactsPath("etc/bootloader_");
+  if(target_arch == Arch::Arm) {
+      default_bootloader += "arm";
+  } else if (target_arch == Arch::Arm64) {
+      default_bootloader += "aarch64";
+  } else {
+      default_bootloader += "x86_64";
+  }
+  default_bootloader += "/bootloader.qemu";
   SetCommandLineOptionWithMode("bootloader", default_bootloader.c_str(),
                                SET_FLAGS_DEFAULT);
 }
@@ -865,19 +931,11 @@
     SetCommandLineOptionWithMode("start_webrtc", "true", SET_FLAGS_DEFAULT);
   }
 
-  // for now, we support only x86_64 by default
-  bool default_enable_sandbox = false;
   std::set<Arch> supported_archs{Arch::X86_64};
-  if (supported_archs.find(HostArch()) != supported_archs.end()) {
-    if (DirectoryExists(kCrosvmVarEmptyDir)) {
-      default_enable_sandbox = IsDirectoryEmpty(kCrosvmVarEmptyDir);
-    } else if (FileExists(kCrosvmVarEmptyDir)) {
-      default_enable_sandbox = false;
-    } else {
-      default_enable_sandbox = EnsureDirectoryExists(kCrosvmVarEmptyDir);
-    }
-  }
-
+  bool default_enable_sandbox =
+      supported_archs.find(HostArch()) != supported_archs.end() &&
+      EnsureDirectoryExists(kCrosvmVarEmptyDir) &&
+      IsDirectoryEmpty(kCrosvmVarEmptyDir) && !IsRunningInContainer();
   SetCommandLineOptionWithMode("enable_sandbox",
                                (default_enable_sandbox ? "true" : "false"),
                                SET_FLAGS_DEFAULT);
@@ -887,10 +945,55 @@
                                SET_FLAGS_DEFAULT);
 }
 
-bool ParseCommandLineFlags(int* argc, char*** argv, KernelConfig* kernel_config) {
-  google::ParseCommandLineNonHelpFlags(argc, argv, true);
+fruit::Component<> FlagsComponent() {
+  return fruit::createComponent().install(GflagsComponent);
+}
+
+bool ParseCommandLineFlags(std::vector<std::string>& args,
+                           KernelConfig* kernel_config) {
+  bool help = false;
+  std::string help_str;
+  bool helpxml = false;
+
+  std::vector<Flag> help_flags = {
+      GflagsCompatFlag("help", help),
+      GflagsCompatFlag("helpfull", help),
+      GflagsCompatFlag("helpshort", help),
+      GflagsCompatFlag("helpmatch", help_str),
+      GflagsCompatFlag("helpon", help_str),
+      GflagsCompatFlag("helppackage", help_str),
+      GflagsCompatFlag("helpxml", helpxml),
+  };
+  for (const auto& help_flag : help_flags) {
+    if (!help_flag.Parse(args)) {
+      LOG(ERROR) << "Failed to process help flag.";
+      return false;
+    }
+  }
+
+  fruit::Injector<> injector(FlagsComponent);
+  auto flag_features = injector.getMultibindings<FlagFeature>();
+  if (!FlagFeature::ProcessFlags(flag_features, args)) {
+    LOG(ERROR) << "Failed to parse flags.";
+    return false;
+  }
+
   SetDefaultFlagsFromConfigPreset();
-  google::HandleCommandLineHelpFlags();
+
+  if (help || help_str != "") {
+    LOG(WARNING) << "TODO(schuffelen): Implement `--help` for assemble_cvd.";
+    LOG(WARNING) << "In the meantime, call `launch_cvd --help`";
+    return false;
+  } else if (helpxml) {
+    if (!FlagFeature::WriteGflagsHelpXml(flag_features, std::cout)) {
+      LOG(ERROR) << "Failure in writing gflags helpxml output";
+    }
+    std::exit(1);  // For parity with gflags
+  }
+  // TODO(schuffelen): Put in "unknown flag" guards after gflags is removed.
+  // gflags either consumes all arguments that start with - or leaves all of
+  // them in place, and either errors out on unknown flags or accepts any flags.
+
   bool invalid_manager = false;
 
   if (!ResolveInstanceFiles()) {
@@ -907,7 +1010,7 @@
   }
 
   if (FLAGS_vm_manager == QemuManager::name()) {
-    SetDefaultFlagsForQemu();
+    SetDefaultFlagsForQemu(kernel_config->target_arch);
   } else if (FLAGS_vm_manager == CrosvmManager::name()) {
     SetDefaultFlagsForCrosvm();
   } else {
diff --git a/host/commands/assemble_cvd/flags.h b/host/commands/assemble_cvd/flags.h
index 3c02d92..70be3a1 100644
--- a/host/commands/assemble_cvd/flags.h
+++ b/host/commands/assemble_cvd/flags.h
@@ -2,6 +2,8 @@
 
 #include <cstdint>
 #include <optional>
+#include <string>
+#include <vector>
 
 #include "common/libs/utils/environment.h"
 #include "host/libs/config/cuttlefish_config.h"
@@ -14,7 +16,7 @@
   bool bootconfig_supported;
 };
 
-bool ParseCommandLineFlags(int* argc, char*** argv,
+bool ParseCommandLineFlags(std::vector<std::string>& flags,
                            KernelConfig* kernel_config);
 // Must be called after ParseCommandLineFlags.
 CuttlefishConfig InitializeCuttlefishConfiguration(
diff --git a/host/commands/bt_connector/OWNERS b/host/commands/bt_connector/OWNERS
index e8a4a00..e791d83 100644
--- a/host/commands/bt_connector/OWNERS
+++ b/host/commands/bt_connector/OWNERS
@@ -1,2 +1,3 @@
+include device/google/cuttlefish:/OWNERS
 include platform/system/bt:/OWNERS
 jeongik@google.com
\ No newline at end of file
diff --git a/host/commands/stop_cvd/Android.bp b/host/commands/cvd/Android.bp
similarity index 68%
copy from host/commands/stop_cvd/Android.bp
copy to host/commands/cvd/Android.bp
index a670a25..c8b9713 100644
--- a/host/commands/stop_cvd/Android.bp
+++ b/host/commands/cvd/Android.bp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2018 The Android Open Source Project
+// 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.
@@ -17,22 +17,30 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-cc_binary {
-    name: "stop_cvd",
+cc_binary_host {
+    name: "cvd",
     srcs: [
         "main.cc",
     ],
     shared_libs: [
         "libbase",
-        "libcuttlefish_fs",
         "libcuttlefish_utils",
-        "libcuttlefish_allocd_utils",
         "libjsoncpp",
     ],
     static_libs: [
         "libcuttlefish_host_config",
-        "libcuttlefish_vm_manager",
-        "libgflags",
     ],
-    defaults: ["cuttlefish_host", "cuttlefish_libicuuc"],
+    required: [
+        "cvd_internal_host_bugreport",
+        "cvd_internal_start",
+        "cvd_internal_status",
+        "cvd_internal_stop",
+    ],
+    symlinks: [
+        "cvd_host_bugreport",
+        "cvd_status",
+        "launch_cvd",
+        "stop_cvd",
+    ],
+    defaults: ["cuttlefish_host"],
 }
diff --git a/host/commands/cvd/main.cc b/host/commands/cvd/main.cc
new file mode 100644
index 0000000..a933616
--- /dev/null
+++ b/host/commands/cvd/main.cc
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/libs/config/cuttlefish_config.h"
+
+namespace cuttlefish {
+namespace {
+
+constexpr char kHostBugreportBin[] = "cvd_internal_host_bugreport";
+constexpr char kStartBin[] = "cvd_internal_start";
+constexpr char kStatusBin[] = "cvd_internal_status";
+constexpr char kStopBin[] = "cvd_internal_stop";
+
+constexpr char kHelpBin[] = "help_placeholder";  // Unused, prints kHelpMessage.
+constexpr char kHelpMessage[] = R"(Cuttlefish Virtual Device (CVD) CLI.
+
+usage: cvd <command>
+
+Commands:
+  help                Print this message.
+  help <command>      Print help for a command.
+  start               Start a device.
+  stop                Stop a running device.
+  status              Check and print the state of a running instance.
+  host_bugreport      Capture a host bugreport, including configs, logs, and tombstones.
+)";
+
+const std::map<std::string, std::string> CommandToBinaryMap = {
+    {"help", kHelpBin},
+    {"host_bugreport", kHostBugreportBin},
+    {"cvd_host_bugreport", kHostBugreportBin},
+    {"start", kStartBin},
+    {"launch_cvd", kStartBin},
+    {"status", kStatusBin},
+    {"cvd_status", kStatusBin},
+    {"stop", kStopBin},
+    {"stop_cvd", kStopBin}};
+
+int CvdMain(int argc, char** argv) {
+  ::android::base::InitLogging(argv, android::base::StderrLogger);
+  std::vector<Flag> flags;
+
+  std::string bin;
+  std::string program_name = cpp_basename(argv[0]);
+  std::string subcommand_name = program_name;
+  if (program_name == "cvd") {
+    if (argc == 1) {
+      // Show help if user invokes `cvd` alone.
+      subcommand_name = "help";
+    } else {
+      subcommand_name = argv[1];
+    }
+  }
+  auto subcommand_bin = CommandToBinaryMap.find(subcommand_name);
+  if (subcommand_bin == CommandToBinaryMap.end()) {
+    // Show help if subcommand not found.
+    bin = kHelpBin;
+  } else {
+    bin = subcommand_bin->second;
+  }
+
+  // Allow `cvd --help` and `cvd help --help`.
+  if (bin == kHelpBin) {
+    flags.emplace_back(HelpFlag(flags, kHelpMessage));
+  }
+
+  // Collect args, skipping the program name.
+  size_t args_to_skip = program_name == "cvd" ? 2 : 1;
+  std::vector<std::string> args =
+      ArgsToVec(argc - args_to_skip, argv + args_to_skip);
+  CHECK(ParseFlags(flags, args));
+
+  if (bin == kHelpBin) {
+    // Handle `cvd help`
+    if (args.empty()) {
+      std::cout << kHelpMessage;
+      return 0;
+    }
+
+    // Handle `cvd help <subcommand>` by calling the subcommand with --help.
+    auto it = CommandToBinaryMap.find(args[0]);
+    if (it == CommandToBinaryMap.end() || args[0] == "help") {
+      std::cout << kHelpMessage;
+      return 0;
+    }
+    bin = it->second;
+    args = {"--help"};
+  }
+
+  setenv(
+      "ANDROID_SOONG_HOST_OUT",
+      android::base::Dirname(android::base::GetExecutableDirectory()).c_str(),
+      /*overwrite=*/true);
+  Command command(HostBinaryPath(bin));
+  for (const std::string& arg : args) {
+    command.AddParameter(arg);
+  }
+  return command.Start().Wait();
+}
+
+}  // namespace
+}  // namespace cuttlefish
+
+int main(int argc, char** argv) { return cuttlefish::CvdMain(argc, argv); }
diff --git a/host/commands/cvd_status/cvd_status.cc b/host/commands/cvd_status/cvd_status.cc
deleted file mode 100644
index 81436d4..0000000
--- a/host/commands/cvd_status/cvd_status.cc
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2018 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 <inttypes.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <signal.h>
-
-#include <algorithm>
-#include <cstdlib>
-#include <fstream>
-#include <iomanip>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include <gflags/gflags.h>
-#include <android-base/logging.h>
-
-#include "common/libs/fs/shared_fd.h"
-#include "common/libs/fs/shared_select.h"
-#include "common/libs/utils/environment.h"
-#include "host/commands/run_cvd/runner_defs.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/vm_manager/vm_manager.h"
-
-DEFINE_int32(wait_for_launcher, 5,
-             "How many seconds to wait for the launcher to respond to the status "
-             "command. A value of zero means wait indefinetly");
-
-int main(int argc, char** argv) {
-  ::android::base::InitLogging(argv, android::base::StderrLogger);
-  google::ParseCommandLineFlags(&argc, &argv, true);
-
-  auto config = cuttlefish::CuttlefishConfig::Get();
-  if (!config) {
-    LOG(ERROR) << "Failed to obtain config object";
-    return 1;
-  }
-
-  auto instance = config->ForDefaultInstance();
-  auto monitor_path = instance.launcher_monitor_socket_path();
-  if (monitor_path.empty()) {
-    LOG(ERROR) << "No path to launcher monitor found";
-    return 2;
-  }
-  auto monitor_socket = cuttlefish::SharedFD::SocketLocalClient(
-      monitor_path.c_str(), false, SOCK_STREAM, FLAGS_wait_for_launcher);
-  if (!monitor_socket->IsOpen()) {
-    LOG(ERROR) << "Unable to connect to launcher monitor at " << monitor_path
-               << ": " << monitor_socket->StrError();
-    return 3;
-  }
-  auto request = cuttlefish::LauncherAction::kStatus;
-  auto bytes_sent = monitor_socket->Send(&request, sizeof(request), 0);
-  if (bytes_sent < 0) {
-    LOG(ERROR) << "Error sending launcher monitor the status command: "
-               << monitor_socket->StrError();
-    return 4;
-  }
-  // Perform a select with a timeout to guard against launcher hanging
-  cuttlefish::SharedFDSet read_set;
-  read_set.Set(monitor_socket);
-  struct timeval timeout = {FLAGS_wait_for_launcher, 0};
-  int selected = cuttlefish::Select(&read_set, nullptr, nullptr,
-                             FLAGS_wait_for_launcher <= 0 ? nullptr : &timeout);
-  if (selected < 0){
-    LOG(ERROR) << "Failed communication with the launcher monitor: "
-               << strerror(errno);
-    return 5;
-  }
-  if (selected == 0) {
-    LOG(ERROR) << "Timeout expired waiting for launcher monitor to respond";
-    return 6;
-  }
-  cuttlefish::LauncherResponse response;
-  auto bytes_recv = monitor_socket->Recv(&response, sizeof(response), 0);
-  if (bytes_recv < 0) {
-    LOG(ERROR) << "Error receiving response from launcher monitor: "
-               << monitor_socket->StrError();
-    return 7;
-  }
-  if (response != cuttlefish::LauncherResponse::kSuccess) {
-    LOG(ERROR) << "Received '" << static_cast<char>(response)
-               << "' response from launcher monitor";
-    return 8;
-  }
-  LOG(INFO) << "run_cvd is active.";
-  return 0;
-}
diff --git a/host/commands/fetcher/build_api.cc b/host/commands/fetcher/build_api.cc
index 69229f0..16f95db 100644
--- a/host/commands/fetcher/build_api.cc
+++ b/host/commands/fetcher/build_api.cc
@@ -128,16 +128,27 @@
 }
 
 std::vector<Artifact> BuildApi::Artifacts(const DeviceBuild& build) {
-  std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target
-      + "/attempts/latest/artifacts?maxResults=1000";
-  auto artifacts_json = curl.DownloadToJson(url, Headers());
-  CHECK(!artifacts_json.isMember("error")) << "Error fetching the artifacts of "
-      << build << ". Response was " << artifacts_json;
-
+  std::string page_token = "";
   std::vector<Artifact> artifacts;
-  for (const auto& artifact_json : artifacts_json["artifacts"]) {
-    artifacts.emplace_back(artifact_json);
-  }
+  do {
+    std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target +
+                      "/attempts/latest/artifacts?maxResults=1000";
+    if (page_token != "") {
+      url += "&pageToken=" + page_token;
+    }
+    auto artifacts_json = curl.DownloadToJson(url, Headers());
+    CHECK(!artifacts_json.isMember("error"))
+        << "Error fetching the artifacts of " << build << ". Response was "
+        << artifacts_json;
+    if (artifacts_json.isMember("nextPageToken")) {
+      page_token = artifacts_json["nextPageToken"].asString();
+    } else {
+      page_token = "";
+    }
+    for (const auto& artifact_json : artifacts_json["artifacts"]) {
+      artifacts.emplace_back(artifact_json);
+    }
+  } while (page_token != "");
   return artifacts;
 }
 
@@ -164,22 +175,17 @@
 bool BuildApi::ArtifactToFile(const DeviceBuild& build,
                               const std::string& artifact,
                               const std::string& path) {
-  std::string url;
-  if (credential_source) {
-    url = BUILD_API + "/builds/" + build.id + "/" + build.target +
-          "/attempts/latest/artifacts/" + artifact + "?alt=media";
-  } else {
-    std::string download_url_endpoint =
-        BUILD_API + "/builds/" + build.id + "/" + build.target +
-        "/attempts/latest/artifacts/" + artifact + "/url";
-    auto download_url_json = curl.DownloadToJson(download_url_endpoint);
-    if (!download_url_json.isMember("signedUrl")) {
-      LOG(ERROR) << "URL endpoint did not have json path";
-      return false;
-    }
-    url = download_url_json["signedUrl"].asString();
+  std::string download_url_endpoint =
+      BUILD_API + "/builds/" + build.id + "/" + build.target +
+      "/attempts/latest/artifacts/" + artifact + "/url";
+  auto download_url_json =
+      curl.DownloadToJson(download_url_endpoint, Headers());
+  if (!download_url_json.isMember("signedUrl")) {
+    LOG(ERROR) << "URL endpoint did not have json path: " << download_url_json;
+    return false;
   }
-  return curl.DownloadToFile(url, path, Headers());
+  std::string url = download_url_json["signedUrl"].asString();
+  return curl.DownloadToFile(url, path);
 }
 
 bool BuildApi::ArtifactToFile(const DirectoryBuild& build,
diff --git a/host/commands/cvd_status/Android.bp b/host/commands/health/Android.bp
similarity index 90%
rename from host/commands/cvd_status/Android.bp
rename to host/commands/health/Android.bp
index ae48d19..1fa59ab 100644
--- a/host/commands/cvd_status/Android.bp
+++ b/host/commands/health/Android.bp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2018 The Android Open Source Project
+// 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.
@@ -18,9 +18,9 @@
 }
 
 cc_binary {
-    name: "cvd_status",
+    name: "health",
     srcs: [
-        "cvd_status.cc",
+        "health.cpp",
     ],
     shared_libs: [
         "libbase",
diff --git a/host/commands/health/health.cpp b/host/commands/health/health.cpp
new file mode 100644
index 0000000..37cce86
--- /dev/null
+++ b/host/commands/health/health.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <iostream>
+//
+#include <android-base/logging.h>
+#include <gflags/gflags.h>
+//
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/vm_manager/vm_manager.h"
+
+std::string GetControlSocketPath(const cuttlefish::CuttlefishConfig& config) {
+  return config.ForDefaultInstance().PerInstanceInternalPath(
+      "crosvm_control.sock");
+}
+
+std::string USAGE_MESSAGE =
+    "<key> [value]\n"
+    "Excluding the value will enumerate the possible values to set\n"
+    "\n"
+    "\"status [value]\" - battery status: "
+    "unknown/charging/discharging/notcharging/full\n"
+    "\"health [value]\" - battery health\n"
+    "\"present [value]\" - battery present: 1 or 0\n"
+    "\"capacity [value]\" - battery capacity: 0 to 100\n"
+    "\"aconline [value]\" - battery ac online: 1 or 0\n";
+
+int status() {
+  std::cout
+      << "health status [value]\n"
+         "\"value\" - unknown, charging, discharging, notcharging, full\n";
+  return 0;
+}
+
+int health() {
+  std::cout << "health health [value]\n"
+               "\"value\" - unknown, good, overheat, dead, overvoltage, "
+               "unexpectedfailure,\n"
+               "          cold, watchdogtimerexpire, safetytimerexpire, "
+               "overcurrent\n";
+  return 0;
+}
+
+int present() {
+  std::cout << "health present [value]\n"
+               "\"value\" - 1, 0\n";
+  return 0;
+}
+
+int capacity() {
+  std::cout << "health capacity [value]\n"
+               "\"value\" - 0 to 100\n";
+  return 0;
+}
+
+int aconline() {
+  std::cout << "health aconline [value]\n"
+               "\"value\" - 1, 0\n";
+  return 0;
+}
+
+int usage() {
+  std::cout << "health " << USAGE_MESSAGE;
+  return 1;
+}
+
+int main(int argc, char** argv) {
+  ::android::base::InitLogging(argv, android::base::StderrLogger);
+  gflags::SetUsageMessage(USAGE_MESSAGE);
+
+  auto config = cuttlefish::CuttlefishConfig::Get();
+  if (!config) {
+    LOG(ERROR) << "Failed to obtain config object";
+    return 1;
+  }
+
+  if (argc != 2 && argc != 3) {
+    return usage();
+  }
+
+  std::string key = argv[1];
+  std::string value = "";
+  if (argc == 3) {
+    value = argv[2];
+  }
+
+  if (argc == 2 || value == "--help" || value == "-h" || value == "help") {
+    if (key == "status") {
+      return status();
+    } else if (key == "health") {
+      return health();
+    } else if (key == "present") {
+      return present();
+    } else if (key == "capacity") {
+      return capacity();
+    } else if (key == "aconline") {
+      return aconline();
+    } else {
+      return usage();
+    }
+  }
+
+  cuttlefish::Command command(config->crosvm_binary());
+  command.AddParameter("battery");
+  command.AddParameter("goldfish");
+  command.AddParameter(key);
+  command.AddParameter(value);
+  command.AddParameter(GetControlSocketPath(*config));
+
+  std::string output, error;
+  auto ret = RunWithManagedStdio(std::move(command), NULL, &output, &error);
+  if (ret != 0) {
+    LOG(ERROR) << "goldfish battery returned: " << ret << "\n" << output << "\n" << error;
+  }
+  return ret;
+}
diff --git a/host/commands/cvd_host_bugreport/Android.bp b/host/commands/host_bugreport/Android.bp
similarity index 96%
rename from host/commands/cvd_host_bugreport/Android.bp
rename to host/commands/host_bugreport/Android.bp
index 4f6aeda..3428360 100644
--- a/host/commands/cvd_host_bugreport/Android.bp
+++ b/host/commands/host_bugreport/Android.bp
@@ -18,7 +18,7 @@
 }
 
 cc_binary {
-    name: "cvd_host_bugreport",
+    name: "cvd_internal_host_bugreport",
     srcs: [
         "main.cc",
     ],
diff --git a/host/commands/cvd_host_bugreport/main.cc b/host/commands/host_bugreport/main.cc
similarity index 100%
rename from host/commands/cvd_host_bugreport/main.cc
rename to host/commands/host_bugreport/main.cc
diff --git a/host/commands/kernel_log_monitor/kernel_log_server.cc b/host/commands/kernel_log_monitor/kernel_log_server.cc
index 66fafab..3dbbe63 100644
--- a/host/commands/kernel_log_monitor/kernel_log_server.cc
+++ b/host/commands/kernel_log_monitor/kernel_log_server.cc
@@ -16,7 +16,8 @@
 
 #include "host/commands/kernel_log_monitor/kernel_log_server.h"
 
-#include <map>
+#include <string>
+#include <tuple>
 #include <utility>
 
 #include <android-base/logging.h>
@@ -25,26 +26,42 @@
 #include "common/libs/fs/shared_select.h"
 #include "host/libs/config/cuttlefish_config.h"
 
-using cuttlefish::SharedFD;
-
 namespace {
-static const std::map<std::string, std::string> kInformationalPatterns = {
+
+using cuttlefish::SharedFD;
+using monitor::Event;
+
+constexpr struct {
+  std::string_view match;   // Substring to match in the kernel logs
+  std::string_view prefix;  // Prefix value to output, describing the entry
+} kInformationalPatterns[] = {
     {"U-Boot ", "GUEST_UBOOT_VERSION: "},
     {"] Linux version ", "GUEST_KERNEL_VERSION: "},
     {"GUEST_BUILD_FINGERPRINT: ", "GUEST_BUILD_FINGERPRINT: "},
 };
 
-static const std::map<std::string, monitor::Event> kStageToEventMap = {
-    {cuttlefish::kBootStartedMessage, monitor::Event::BootStarted},
-    {cuttlefish::kBootCompletedMessage, monitor::Event::BootCompleted},
-    {cuttlefish::kBootFailedMessage, monitor::Event::BootFailed},
-    {cuttlefish::kMobileNetworkConnectedMessage,
-     monitor::Event::MobileNetworkConnected},
-    {cuttlefish::kWifiConnectedMessage, monitor::Event::WifiNetworkConnected},
-    {cuttlefish::kEthernetConnectedMessage, monitor::Event::EthernetNetworkConnected},
+enum EventFormat {
+  kBare,          // Just an event, no extra data
+  kKeyValuePair,  // <stage> <key>=<value>
+};
+
+constexpr struct {
+  std::string_view stage;  // substring in the log identifying the stage
+  Event event;             // emitted when the stage is encountered
+  EventFormat format;      // how the log message is formatted
+} kStageTable[] = {
+    {cuttlefish::kBootStartedMessage, Event::BootStarted, kBare},
+    {cuttlefish::kBootCompletedMessage, Event::BootCompleted, kBare},
+    {cuttlefish::kBootFailedMessage, Event::BootFailed, kKeyValuePair},
+    {cuttlefish::kMobileNetworkConnectedMessage, Event::MobileNetworkConnected,
+     kBare},
+    {cuttlefish::kWifiConnectedMessage, Event::WifiNetworkConnected, kBare},
+    {cuttlefish::kEthernetConnectedMessage, Event::EthernetNetworkConnected,
+     kBare},
     // TODO(b/131864854): Replace this with a string less likely to change
-    {"init: starting service 'adbd'...", monitor::Event::AdbdStarted},
-    {cuttlefish::kScreenChangedMessage, monitor::Event::ScreenChanged},
+    {"init: starting service 'adbd'...", Event::AdbdStarted, kBare},
+    {cuttlefish::kScreenChangedMessage, Event::ScreenChanged, kKeyValuePair},
+    {cuttlefish::kKernelLoadedMessage, Event::KernelLoaded, kBare},
 };
 
 void ProcessSubscriptions(
@@ -109,17 +126,13 @@
   // Detect VIRTUAL_DEVICE_BOOT_*
   for (ssize_t i=0; i<ret; i++) {
     if ('\n' == buf[i]) {
-      for (auto& info_kv : kInformationalPatterns) {
-        auto& match = info_kv.first;
-        auto& prefix = info_kv.second;
+      for (auto& [match, prefix] : kInformationalPatterns) {
         auto pos = line_.find(match);
         if (std::string::npos != pos) {
           LOG(INFO) << prefix << line_.substr(pos + match.size());
         }
       }
-      for (auto& stage_kv : kStageToEventMap) {
-        auto& stage = stage_kv.first;
-        auto event = stage_kv.second;
+      for (const auto& [stage, event, format] : kStageTable) {
         auto pos = line_.find(stage);
         if (std::string::npos != pos) {
           // Log the stage
@@ -128,23 +141,26 @@
           Json::Value message;
           message["event"] = event;
           Json::Value metadata;
-          // Expect space-separated key=value pairs in the log message.
-          const auto& fields = android::base::Split(
-              line_.substr(pos + stage.size()), " ");
-          for (std::string field : fields) {
-            field = android::base::Trim(field);
-            if (field.empty()) {
-              // Expected; android::base::Split() always returns at least
-              // one (possibly empty) string.
-              LOG(DEBUG) << "Empty field for line: " << line_;
-              continue;
+
+          if (format == kKeyValuePair) {
+            // Expect space-separated key=value pairs in the log message.
+            const auto& fields =
+                android::base::Split(line_.substr(pos + stage.size()), " ");
+            for (std::string field : fields) {
+              field = android::base::Trim(field);
+              if (field.empty()) {
+                // Expected; android::base::Split() always returns at least
+                // one (possibly empty) string.
+                LOG(DEBUG) << "Empty field for line: " << line_;
+                continue;
+              }
+              const auto& keyvalue = android::base::Split(field, "=");
+              if (keyvalue.size() != 2) {
+                LOG(WARNING) << "Field is not in key=value format: " << field;
+                continue;
+              }
+              metadata[keyvalue[0]] = keyvalue[1];
             }
-            const auto& keyvalue = android::base::Split(field, "=");
-            if (keyvalue.size() != 2) {
-              LOG(WARNING) << "Field is not in key=value format: " << field;
-              continue;
-            }
-            metadata[keyvalue[0]] = keyvalue[1];
           }
           message["metadata"] = metadata;
           ProcessSubscriptions(message, &subscribers_);
@@ -154,7 +170,7 @@
           if (deprecated_boot_completed_) {
             // Write to host kernel log
             FILE* log = popen("/usr/bin/sudo /usr/bin/tee /dev/kmsg", "w");
-            fprintf(log, "%s\n", stage.c_str());
+            fprintf(log, "%s\n", std::string(stage).c_str());
             fclose(log);
           }
         }
diff --git a/host/commands/kernel_log_monitor/kernel_log_server.h b/host/commands/kernel_log_monitor/kernel_log_server.h
index 659a372..c3c6b3f 100644
--- a/host/commands/kernel_log_monitor/kernel_log_server.h
+++ b/host/commands/kernel_log_monitor/kernel_log_server.h
@@ -37,6 +37,9 @@
   AdbdStarted = 5,
   ScreenChanged = 6,
   EthernetNetworkConnected = 7,
+  KernelLoaded = 8,  // BootStarted actually comes quite late in the boot.
+                     // KernelLoaded is the earliest possible indicator
+                     // that we're booting a device.
 };
 
 enum class SubscriptionAction {
diff --git a/host/commands/mk_cdisk/Android.bp b/host/commands/mk_cdisk/Android.bp
index 266dd15..a0cf8ba 100644
--- a/host/commands/mk_cdisk/Android.bp
+++ b/host/commands/mk_cdisk/Android.bp
@@ -27,13 +27,14 @@
         "libcuttlefish_utils",
         "libbase",
         "libjsoncpp",
-        "libprotobuf-cpp-full",
+        "liblog",
         "libz",
     ],
     static_libs: [
         "libcdisk_spec",
         "libext2_uuid",
         "libimage_aggregator",
+        "libprotobuf-cpp-lite",
         "libsparse",
     ],
     defaults: ["cuttlefish_host"],
diff --git a/host/commands/mk_cdisk/mk_cdisk.cc b/host/commands/mk_cdisk/mk_cdisk.cc
index a9949dc..028547c 100644
--- a/host/commands/mk_cdisk/mk_cdisk.cc
+++ b/host/commands/mk_cdisk/mk_cdisk.cc
@@ -50,7 +50,7 @@
 //     {
 //       "label": string,
 //       "path": string,
-//       (opt) "read_only": bool
+//       "writable": bool, // optional. defaults to false.
 //     }
 //   ]
 // }
@@ -67,14 +67,14 @@
   for (const Json::Value& part : root["partitions"]) {
     const std::string label = part["label"].asString();
     const std::string path = part["path"].asString();
-    const bool read_only =
-        part["read_only"].asBool();  // default: false (if null)
+    const bool writable =
+        part["writable"].asBool();  // default: false (if null)
 
     if (!FileExists(path)) {
       return Error() << "bad config: Can't find \'" << path << '\'';
     }
     partitions.push_back(
-        ImagePartition{label, path, kLinuxFilesystem, read_only});
+        ImagePartition{label, path, kLinuxFilesystem, .read_only = !writable});
   }
 
   if (partitions.empty()) {
diff --git a/host/commands/modem_simulator/call_service.cpp b/host/commands/modem_simulator/call_service.cpp
index 678da4f..3dd6076 100644
--- a/host/commands/modem_simulator/call_service.cpp
+++ b/host/commands/modem_simulator/call_service.cpp
@@ -194,7 +194,7 @@
       client.SendCommandResponse(kCmeErrorNoNetworkService);
       return;
     }
-    auto local_host_port = GetHostPort();
+    auto local_host_port = GetHostId();
     if (local_host_port == remote_port) {
       client.SendCommandResponse(kCmeErrorOperationNotAllowed);
       return;
@@ -249,11 +249,9 @@
                                          CallStatus::CallState state) {
   if (call.is_remote_call && call.remote_client != std::nullopt) {
     std::stringstream ss;
-    ss << "AT+REMOTECALL=" << state << ","
-                           << call.is_voice_mode << ","
-                           << call.is_multi_party << ",\""
-                           << GetHostPort() << "\","
-                           << call.is_international;
+    ss << "AT+REMOTECALL=" << state << "," << call.is_voice_mode << ","
+       << call.is_multi_party << ",\"" << GetHostId() << "\","
+       << call.is_international;
 
     SendCommandToRemote(*(call.remote_client), ss.str());
     if (state == CallStatus::CALL_STATE_HANGUP) {
diff --git a/host/commands/modem_simulator/cf_device_config.cpp b/host/commands/modem_simulator/cf_device_config.cpp
index 559e1f8..81c60e7 100644
--- a/host/commands/modem_simulator/cf_device_config.cpp
+++ b/host/commands/modem_simulator/cf_device_config.cpp
@@ -22,14 +22,13 @@
 namespace cuttlefish {
 namespace modem {
 
-int DeviceConfig::host_port() {
+int DeviceConfig::host_id() {
   if (!cuttlefish::CuttlefishConfig::Get()) {
-      return 6500;
+    return 1000;
   }
   auto config = cuttlefish::CuttlefishConfig::Get();
   auto instance = config->ForDefaultInstance();
-  auto host_port = instance.host_port();
-  return host_port;
+  return instance.modem_simulator_host_id();
 }
 
 std::string DeviceConfig::PerInstancePath(const char* file_name) {
diff --git a/host/commands/modem_simulator/channel_monitor.cpp b/host/commands/modem_simulator/channel_monitor.cpp
index e598f2d..941a8b4 100644
--- a/host/commands/modem_simulator/channel_monitor.cpp
+++ b/host/commands/modem_simulator/channel_monitor.cpp
@@ -45,7 +45,7 @@
   if (response.back() != '\r') {
     response += '\r';
   }
-  LOG(VERBOSE) << " AT< " << response;
+  LOG(DEBUG) << " AT< " << response;
 
   std::lock_guard<std::mutex> autolock(const_cast<Client*>(this)->write_mutex);
   client_fd->Write(response.data(), response.size());
@@ -153,7 +153,7 @@
     if (r_pos != std::string::npos) {
       auto command = commands.substr(pos, r_pos - pos);
       if (command.size() > 0) {  // "\r\r" ?
-        LOG(VERBOSE) << "AT> " << command;
+        LOG(DEBUG) << "AT> " << command;
         modem_->DispatchCommand(client, command);
       }
       pos = r_pos + 1;  // Skip '\r'
diff --git a/host/commands/modem_simulator/device_config.h b/host/commands/modem_simulator/device_config.h
index 05bca77..d13a38a 100644
--- a/host/commands/modem_simulator/device_config.h
+++ b/host/commands/modem_simulator/device_config.h
@@ -26,7 +26,7 @@
 
 class DeviceConfig {
  public:
-  static int host_port();
+  static int host_id();
   static std::string PerInstancePath(const char* file_name);
   static std::string DefaultHostArtifactsPath(const std::string& file);
   static std::string ril_address_and_prefix();
diff --git a/host/commands/modem_simulator/files/iccprofile_for_sim0.xml b/host/commands/modem_simulator/files/iccprofile_for_sim0.xml
index 42fadf7..655c37a 100755
--- a/host/commands/modem_simulator/files/iccprofile_for_sim0.xml
+++ b/host/commands/modem_simulator/files/iccprofile_for_sim0.xml
@@ -23,6 +23,7 @@
             <EF name="EF_PBR" id="4F30" structure="linear fixed">
                 <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,62198205422100400283024F308A01058B036F0606800200808800</SIMIO>
                 <SIMIO cmd="B2" p1="1" p2="4" p3="40" data="">144,0,A81EC0034F3A01C1034F3306C5034F0902C4034F1104C6034F2503C9034F3107A905CA034F5008AA0FC2034F4A09C7034F4B0AC8034F4C0BFFFFFFFFFFFFFFFF</SIMIO>
+                <SIMIO cmd="B2" p1="2" p2="4" p3="40" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
             </EF>
             <EF name="EF_GAS" id="4F4C" structure="linear fixed">
                 <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,621A8205422100140A83024F4C8A01058B036F060E800200C8880158</SIMIO>
@@ -38,7 +39,7 @@
                 <SIMIO cmd="B2" p1="A" p2="4" p3="E" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
             </EF>
             <EF name="EF_ADN" id="4F3A" structure="linear fixed">
-                <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,621A82054221001CFA83024F3A8A01058B036F060E80021B58880108</SIMIO>
+                <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,621A82054221001C1483024F3A8A01058B036F060E80020230880108</SIMIO>
                 <SIMIO cmd="B2" p1="1" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
                 <SIMIO cmd="B2" p1="2" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
                 <SIMIO cmd="B2" p1="3" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
@@ -56,7 +57,9 @@
                 <SIMIO cmd="B2" p1="F" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
                 <SIMIO cmd="B2" p1="10" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
                 <SIMIO cmd="B2" p1="11" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
-                <!-- EF_ADN P2=18...250 -->
+                <SIMIO cmd="B2" p1="12" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
+                <SIMIO cmd="B2" p1="13" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
+                <SIMIO cmd="B2" p1="14" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
             </EF>
             <EF name="EF_IAP" id="4F33" structure="linear fixed">
                 <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,621A820542210001FA83024F338A01058B036F060E800200FA880130</SIMIO>
diff --git a/host/commands/modem_simulator/files/iccprofile_for_sim0_for_CtsCarrierApiTestCases.xml b/host/commands/modem_simulator/files/iccprofile_for_sim0_for_CtsCarrierApiTestCases.xml
index 188aca2..b4363da 100755
--- a/host/commands/modem_simulator/files/iccprofile_for_sim0_for_CtsCarrierApiTestCases.xml
+++ b/host/commands/modem_simulator/files/iccprofile_for_sim0_for_CtsCarrierApiTestCases.xml
@@ -23,6 +23,7 @@
             <EF name="EF_PBR" id="4F30" structure="linear fixed">
                 <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,62198205422100400283024F308A01058B036F0606800200808800</SIMIO>
                 <SIMIO cmd="B2" p1="1" p2="4" p3="40" data="">144,0,A81EC0034F3A01C1034F3306C5034F0902C4034F1104C6034F2503C9034F3107A905CA034F5008AA0FC2034F4A09C7034F4B0AC8034F4C0BFFFFFFFFFFFFFFFF</SIMIO>
+                <SIMIO cmd="B2" p1="2" p2="4" p3="40" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
             </EF>
             <EF name="EF_GAS" id="4F4C" structure="linear fixed">
                 <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,621A8205422100140A83024F4C8A01058B036F060E800200C8880158</SIMIO>
@@ -38,7 +39,7 @@
                 <SIMIO cmd="B2" p1="A" p2="4" p3="E" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
             </EF>
             <EF name="EF_ADN" id="4F3A" structure="linear fixed">
-                <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,621A82054221001CFA83024F3A8A01058B036F060E80021B58880108</SIMIO>
+                <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,621A82054221001C1483024F3A8A01058B036F060E80020230880108</SIMIO>
                 <SIMIO cmd="B2" p1="1" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
                 <SIMIO cmd="B2" p1="2" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
                 <SIMIO cmd="B2" p1="3" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
@@ -56,7 +57,9 @@
                 <SIMIO cmd="B2" p1="F" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
                 <SIMIO cmd="B2" p1="10" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
                 <SIMIO cmd="B2" p1="11" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
-                <!-- EF_ADN P2=18...250 -->
+                <SIMIO cmd="B2" p1="12" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
+                <SIMIO cmd="B2" p1="13" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
+                <SIMIO cmd="B2" p1="14" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
             </EF>
             <EF name="EF_IAP" id="4F33" structure="linear fixed">
                 <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,621A820542210001FA83024F338A01058B036F060E800200FA880130</SIMIO>
@@ -124,11 +127,11 @@
 <ADF name="PKCS15" aid="A000000063504B43532D3135">
     <File id="4300">
         <CGLA cmd="00a40004024300">76,62228202412183024300A503C001408A01058B066F0601010001800201DC810201EE88009000</CGLA>
-        <CGLA cmd="00b00000004300">516,30088200300404024310301AA0120410A000000476416E64726F696443545340300404024311301AA0120410A000000476416E64726F696443545341300404024312301AA0120410A000000476416E64726F696443545342300404024313301AA0120410A000000476416E64726F696443545343300404024314301AA0120410A000000476416E64726F696443545344300404024315301AA0120410A000000476416E64726F696443545345300404024316301AA0120410A000000476416E64726F6964435453463004040243173010A0080406FFFFFFFFFFFF300404024318301AA0120410A000000476416E64726F696443545347300404024313301AA0129000</CGLA>
+        <CGLA cmd="00b0000000">516,30088200300404024310301AA0120410A000000476416E64726F696443545340300404024311301AA0120410A000000476416E64726F696443545341300404024312301AA0120410A000000476416E64726F696443545342300404024313301AA0120410A000000476416E64726F696443545343300404024314301AA0120410A000000476416E64726F696443545344300404024315301AA0120410A000000476416E64726F696443545345300404024316301AA0120410A000000476416E64726F6964435453463004040243173010A0080406FFFFFFFFFFFF300404024318301AA0120410A000000476416E64726F696443545347300404024313301AA0129000</CGLA>
     </File>
     <File id="4318">
         <CGLA cmd="00a40004024318">76,62228202412183024318A503C001408A01058B066F0601010001800200188102002A88009000</CGLA>
-        <CGLA cmd="00b00000004318">124,3016041461ED377E85D386A8DFEE6B864BD85B0BFAA5AF8130220420CE7B2B47AE2B7552C8F92CC29124279883041FB623A5F194A82C9BF15D492AA09000</CGLA>
+        <CGLA cmd="00b0000000">124,3016041461ED377E85D386A8DFEE6B864BD85B0BFAA5AF8130220420CE7B2B47AE2B7552C8F92CC29124279883041FB623A5F194A82C9BF15D492AA09000</CGLA>
     </File>
 </ADF>
 
diff --git a/host/commands/modem_simulator/main.cpp b/host/commands/modem_simulator/main.cpp
index 794ad77..8bfcd97 100644
--- a/host/commands/modem_simulator/main.cpp
+++ b/host/commands/modem_simulator/main.cpp
@@ -114,7 +114,7 @@
   // remote call, remote sms from other cuttlefish instance
   std::string monitor_socket_name = "modem_simulator";
   std::stringstream ss;
-  ss << instance.host_port();
+  ss << instance.modem_simulator_host_id();
   monitor_socket_name.append(ss.str());
 
   auto monitor_socket = cuttlefish::SharedFD::SocketLocalServer(
diff --git a/host/commands/modem_simulator/modem_service.cpp b/host/commands/modem_simulator/modem_service.cpp
index 062da6f..7996e31 100644
--- a/host/commands/modem_simulator/modem_service.cpp
+++ b/host/commands/modem_simulator/modem_service.cpp
@@ -137,11 +137,8 @@
   }
 }
 
-std::string ModemService::GetHostPort() {
-  auto host_port = cuttlefish::modem::DeviceConfig::host_port();
-  std::stringstream ss;
-  ss << host_port;
-  return ss.str();
+std::string ModemService::GetHostId() {
+  return std::to_string(cuttlefish::modem::DeviceConfig::host_id());
 }
 
 }  // namespace cuttlefish
diff --git a/host/commands/modem_simulator/modem_service.h b/host/commands/modem_simulator/modem_service.h
index 0b75d7d..744f618 100644
--- a/host/commands/modem_simulator/modem_service.h
+++ b/host/commands/modem_simulator/modem_service.h
@@ -104,7 +104,7 @@
   void SendCommandToRemote(cuttlefish::SharedFD remote_client,
                            std::string response);
   void CloseRemoteConnection(cuttlefish::SharedFD remote_client);
-  static std::string GetHostPort();
+  static std::string GetHostId();
 
   int32_t service_id_;
   const std::vector<CommandHandler> command_handlers_;
diff --git a/host/commands/modem_simulator/network_service.cpp b/host/commands/modem_simulator/network_service.cpp
index fddf5d4..56f14e5 100644
--- a/host/commands/modem_simulator/network_service.cpp
+++ b/host/commands/modem_simulator/network_service.cpp
@@ -57,6 +57,10 @@
                      [this](const Client& client, std::string& cmd) {
                        this->HandleRadioPower(client, cmd);
                      }),
+      CommandHandler("+REMOTECFUN=",
+                     [this](const Client& client, std::string& cmd) {
+                       this->HandleRadioPower(client, cmd);
+                     }),
       CommandHandler(
           "+CSQ",
           [this](const Client& client) { this->HandleSignalStrength(client); }),
diff --git a/host/commands/modem_simulator/sim_service.cpp b/host/commands/modem_simulator/sim_service.cpp
index a4e660d..e6eced5 100644
--- a/host/commands/modem_simulator/sim_service.cpp
+++ b/host/commands/modem_simulator/sim_service.cpp
@@ -911,10 +911,17 @@
     return;
   }
 
+  SimFileSystem::EFId fileid = (SimFileSystem::EFId)std::stoi(id, nullptr, 16);
   if (path == "") {
-    SimFileSystem::EFId fileid = (SimFileSystem::EFId)std::stoi(id, nullptr, 16);
     path = SimFileSystem::GetUsimEFPath(fileid);
   }
+  // EF_ADN under DF_PHONEBOOK is mapped to EF_ADN under DF_TELECOM per
+  // 3GPP TS 31.102 4.4.2
+  if (fileid == SimFileSystem::EF_ADN &&
+      path == SimFileSystem::GetUsimEFPath(fileid)) {
+    id = "4F3A";
+    path = MF_SIM + DF_TELECOM + DF_PHONEBOOK;
+  }
 
   size_t pos = 0;
   auto parent = root;
diff --git a/host/commands/modem_simulator/sms_service.cpp b/host/commands/modem_simulator/sms_service.cpp
index 0da21a6..6a61e7b 100644
--- a/host/commands/modem_simulator/sms_service.cpp
+++ b/host/commands/modem_simulator/sms_service.cpp
@@ -256,8 +256,8 @@
     return;
   }
 
-  auto local_host_port = GetHostPort();
-  auto pdu = sms_pdu.CreateRemotePDU(local_host_port);
+  auto local_host_id = GetHostId();
+  auto pdu = sms_pdu.CreateRemotePDU(local_host_id);
 
   std::string command = "AT+REMOTESMS=" + pdu + "\r";
   std::string token = "REM0";
@@ -292,10 +292,8 @@
     return;
   } else if (port >= kRemotePortRange.first &&
              port <= kRemotePortRange.second) {
-    std::stringstream ss;
-    ss << port;
-    auto remote_host_port = ss.str();
-    if (GetHostPort() == remote_host_port) {  // Send SMS to local host port
+    auto remote_host_port = std::to_string(port);
+    if (GetHostId() == remote_host_port) {  // Send SMS to local host port
       thread_looper_->PostWithDelay(
           std::chrono::seconds(1),
           makeSafeCallback<SmsService>(this, [&sms_pdu](SmsService* me) {
diff --git a/host/commands/modem_simulator/unittest/iccfile.txt b/host/commands/modem_simulator/unittest/iccfile.txt
index dec0da6..48e2a02 100755
--- a/host/commands/modem_simulator/unittest/iccfile.txt
+++ b/host/commands/modem_simulator/unittest/iccfile.txt
@@ -23,6 +23,7 @@
             <EF name="EF_PBR" id="4F30" structure="linear fixed">
                 <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,62198205422100400283024F308A01058B036F0606800200808800</SIMIO>
                 <SIMIO cmd="B2" p1="1" p2="4" p3="40" data="">144,0,A81EC0034F3A01C1034F3306C5034F0902C4034F1104C6034F2503C9034F3107A905CA034F5008AA0FC2034F4A09C7034F4B0AC8034F4C0BFFFFFFFFFFFFFFFF</SIMIO>
+                <SIMIO cmd="B2" p1="2" p2="4" p3="40" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
             </EF>
             <EF name="EF_GAS" id="4F4C" structure="linear fixed">
                 <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,621A8205422100140A83024F4C8A01058B036F060E800200C8880158</SIMIO>
@@ -38,7 +39,7 @@
                 <SIMIO cmd="B2" p1="A" p2="4" p3="E" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
             </EF>
             <EF name="EF_ADN" id="4F3A" structure="linear fixed">
-                <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,621A82054221001CFA83024F3A8A01058B036F060E80021B58880108</SIMIO>
+                <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,621A82054221001C1483024F3A8A01058B036F060E80020230880108</SIMIO>
                 <SIMIO cmd="B2" p1="1" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
                 <SIMIO cmd="B2" p1="2" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
                 <SIMIO cmd="B2" p1="3" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
@@ -56,7 +57,9 @@
                 <SIMIO cmd="B2" p1="F" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
                 <SIMIO cmd="B2" p1="10" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
                 <SIMIO cmd="B2" p1="11" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
-                <!-- EF_ADN P2=18...250 -->
+                <SIMIO cmd="B2" p1="12" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
+                <SIMIO cmd="B2" p1="13" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
+                <SIMIO cmd="B2" p1="14" p2="4" p3="1C" data="">144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SIMIO>
             </EF>
             <EF name="EF_IAP" id="4F33" structure="linear fixed">
                 <SIMIO cmd="C0" p1="0" p2="0" p3="F" data="">144,0,621A820542210001FA83024F338A01058B036F060E800200FA880130</SIMIO>
diff --git a/host/commands/run_cvd/Android.bp b/host/commands/run_cvd/Android.bp
index 6100be5..16864b7 100644
--- a/host/commands/run_cvd/Android.bp
+++ b/host/commands/run_cvd/Android.bp
@@ -22,14 +22,21 @@
     srcs: [
         "boot_state_machine.cc",
         "launch.cc",
+        "launch_adb.cpp",
+        "launch_modem.cpp",
+        "launch_streamer.cpp",
         "main.cc",
+        "reporting.cpp",
         "process_monitor.cc",
+        "server_loop.cpp",
+        "validate.cpp",
     ],
     shared_libs: [
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libcuttlefish_kernel_log_monitor_utils",
         "libbase",
+        "libfruit",
         "libjsoncpp",
         "libnl",
     ],
diff --git a/host/commands/run_cvd/boot_state_machine.cc b/host/commands/run_cvd/boot_state_machine.cc
index 64eb893..2d6fcc9 100644
--- a/host/commands/run_cvd/boot_state_machine.cc
+++ b/host/commands/run_cvd/boot_state_machine.cc
@@ -16,24 +16,181 @@
 
 #include "host/commands/run_cvd/boot_state_machine.h"
 
+#include <gflags/gflags.h>
 #include <memory>
 #include <thread>
 
 #include "android-base/logging.h"
 #include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/tee_logging.h"
 #include "host/commands/kernel_log_monitor/kernel_log_server.h"
 #include "host/commands/kernel_log_monitor/utils.h"
 #include "host/commands/run_cvd/runner_defs.h"
+#include "host/libs/config/feature.h"
+
+DEFINE_int32(reboot_notification_fd, -1,
+             "A file descriptor to notify when boot completes.");
 
 namespace cuttlefish {
+namespace {
 
-CvdBootStateMachine::CvdBootStateMachine(SharedFD fg_launcher_pipe,
-                                         SharedFD reboot_notification,
-                                         SharedFD boot_events_pipe)
-    : fg_launcher_pipe_(fg_launcher_pipe),
-      reboot_notification_(reboot_notification),
-      state_(kBootStarted) {
-  boot_event_handler_ = std::thread([this, boot_events_pipe]() {
+// Forks and returns the write end of a pipe to the child process. The parent
+// process waits for boot events to come through the pipe and exits accordingly.
+SharedFD DaemonizeLauncher(const CuttlefishConfig& config) {
+  auto instance = config.ForDefaultInstance();
+  SharedFD read_end, write_end;
+  if (!SharedFD::Pipe(&read_end, &write_end)) {
+    LOG(ERROR) << "Unable to create pipe";
+    return {};  // a closed FD
+  }
+  auto pid = fork();
+  if (pid) {
+    // Explicitly close here, otherwise we may end up reading forever if the
+    // child process dies.
+    write_end->Close();
+    RunnerExitCodes exit_code;
+    auto bytes_read = read_end->Read(&exit_code, sizeof(exit_code));
+    if (bytes_read != sizeof(exit_code)) {
+      LOG(ERROR) << "Failed to read a complete exit code, read " << bytes_read
+                 << " bytes only instead of the expected " << sizeof(exit_code);
+      exit_code = RunnerExitCodes::kPipeIOError;
+    } else if (exit_code == RunnerExitCodes::kSuccess) {
+      LOG(INFO) << "Virtual device booted successfully";
+    } else if (exit_code == RunnerExitCodes::kVirtualDeviceBootFailed) {
+      LOG(ERROR) << "Virtual device failed to boot";
+    } else {
+      LOG(ERROR) << "Unexpected exit code: " << exit_code;
+    }
+    if (exit_code == RunnerExitCodes::kSuccess) {
+      LOG(INFO) << kBootCompletedMessage;
+    } else {
+      LOG(INFO) << kBootFailedMessage;
+    }
+    std::exit(exit_code);
+  } else {
+    // The child returns the write end of the pipe
+    if (daemon(/*nochdir*/ 1, /*noclose*/ 1) != 0) {
+      LOG(ERROR) << "Failed to daemonize child process: " << strerror(errno);
+      std::exit(RunnerExitCodes::kDaemonizationError);
+    }
+    // Redirect standard I/O
+    auto log_path = instance.launcher_log_path();
+    auto log = SharedFD::Open(log_path.c_str(), O_CREAT | O_WRONLY | O_APPEND,
+                              S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+    if (!log->IsOpen()) {
+      LOG(ERROR) << "Failed to create launcher log file: " << log->StrError();
+      std::exit(RunnerExitCodes::kDaemonizationError);
+    }
+    ::android::base::SetLogger(
+        TeeLogger({{LogFileSeverity(), log, MetadataLevel::FULL}}));
+    auto dev_null = SharedFD::Open("/dev/null", O_RDONLY);
+    if (!dev_null->IsOpen()) {
+      LOG(ERROR) << "Failed to open /dev/null: " << dev_null->StrError();
+      std::exit(RunnerExitCodes::kDaemonizationError);
+    }
+    if (dev_null->UNMANAGED_Dup2(0) < 0) {
+      LOG(ERROR) << "Failed dup2 stdin: " << dev_null->StrError();
+      std::exit(RunnerExitCodes::kDaemonizationError);
+    }
+    if (log->UNMANAGED_Dup2(1) < 0) {
+      LOG(ERROR) << "Failed dup2 stdout: " << log->StrError();
+      std::exit(RunnerExitCodes::kDaemonizationError);
+    }
+    if (log->UNMANAGED_Dup2(2) < 0) {
+      LOG(ERROR) << "Failed dup2 seterr: " << log->StrError();
+      std::exit(RunnerExitCodes::kDaemonizationError);
+    }
+
+    read_end->Close();
+    return write_end;
+  }
+}
+
+class ProcessLeader : public Feature {
+ public:
+  INJECT(ProcessLeader(const CuttlefishConfig& config)) : config_(config) {}
+
+  SharedFD ForegroundLauncherPipe() { return foreground_launcher_pipe_; }
+
+  bool Enabled() const override { return true; }
+  std::string Name() const override { return "ProcessLeader"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override {
+    /* These two paths result in pretty different process state, but both
+     * achieve the same goal of making the current process the leader of a
+     * process group, and are therefore grouped together. */
+    if (config_.run_as_daemon()) {
+      foreground_launcher_pipe_ = DaemonizeLauncher(config_);
+      if (!foreground_launcher_pipe_->IsOpen()) {
+        return false;
+      }
+    } else {
+      // Make sure the launcher runs in its own process group even when running
+      // in the foreground
+      if (getsid(0) != getpid()) {
+        int retval = setpgid(0, 0);
+        if (retval) {
+          PLOG(ERROR) << "Failed to create new process group: ";
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig& config_;
+  SharedFD foreground_launcher_pipe_;
+};
+
+// Maintains the state of the boot process, once a final state is reached
+// (success or failure) it sends the appropriate exit code to the foreground
+// launcher process
+class CvdBootStateMachine : public Feature {
+ public:
+  INJECT(CvdBootStateMachine(ProcessLeader& process_leader,
+                             KernelLogPipeProvider& kernel_log_pipe_provider))
+      : process_leader_(process_leader),
+        kernel_log_pipe_provider_(kernel_log_pipe_provider),
+        state_(kBootStarted) {}
+
+  ~CvdBootStateMachine() { boot_event_handler_.join(); }
+
+  // Feature
+  bool Enabled() const override { return true; }
+  std::string Name() const override { return "CvdBootStateMachine"; }
+  std::unordered_set<Feature*> Dependencies() const {
+    return {
+        static_cast<Feature*>(&process_leader_),
+        static_cast<Feature*>(&kernel_log_pipe_provider_),
+    };
+  }
+
+ protected:
+  bool Setup() override {
+    fg_launcher_pipe_ = process_leader_.ForegroundLauncherPipe();
+    if (FLAGS_reboot_notification_fd >= 0) {
+      reboot_notification_ = SharedFD::Dup(FLAGS_reboot_notification_fd);
+      if (!reboot_notification_->IsOpen()) {
+        LOG(ERROR) << "Could not dup fd given for reboot_notification_fd";
+        return false;
+      }
+      close(FLAGS_reboot_notification_fd);
+    }
+    SharedFD boot_events_pipe = kernel_log_pipe_provider_.KernelLogPipe();
+    if (!boot_events_pipe->IsOpen()) {
+      LOG(ERROR) << "Could not get boot events pipe";
+      return false;
+    }
+    boot_event_handler_ = std::thread(
+        [this, boot_events_pipe]() { ThreadLoop(boot_events_pipe); });
+    return true;
+  }
+
+ private:
+  void ThreadLoop(SharedFD boot_events_pipe) {
     while (true) {
       SharedFDSet fd_set;
       fd_set.Set(boot_events_pipe);
@@ -50,61 +207,72 @@
         break;
       }
     }
-  });
-}
+  }
 
-CvdBootStateMachine::~CvdBootStateMachine() { boot_event_handler_.join(); }
+  // Returns true if the machine is left in a final state
+  bool OnBootEvtReceived(SharedFD boot_events_pipe) {
+    std::optional<monitor::ReadEventResult> read_result =
+        monitor::ReadEvent(boot_events_pipe);
+    if (!read_result) {
+      LOG(ERROR) << "Failed to read a complete kernel log boot event.";
+      state_ |= kGuestBootFailed;
+      return MaybeWriteNotification();
+    }
 
-// Returns true if the machine is left in a final state
-bool CvdBootStateMachine::OnBootEvtReceived(SharedFD boot_events_pipe) {
-  std::optional<monitor::ReadEventResult> read_result =
-      monitor::ReadEvent(boot_events_pipe);
-  if (!read_result) {
-    LOG(ERROR) << "Failed to read a complete kernel log boot event.";
-    state_ |= kGuestBootFailed;
+    if (read_result->event == monitor::Event::BootCompleted) {
+      LOG(INFO) << "Virtual device booted successfully";
+      state_ |= kGuestBootCompleted;
+    } else if (read_result->event == monitor::Event::BootFailed) {
+      LOG(ERROR) << "Virtual device failed to boot";
+      state_ |= kGuestBootFailed;
+    }  // Ignore the other signals
+
     return MaybeWriteNotification();
   }
+  bool BootCompleted() const { return state_ & kGuestBootCompleted; }
+  bool BootFailed() const { return state_ & kGuestBootFailed; }
 
-  if (read_result->event == monitor::Event::BootCompleted) {
-    LOG(INFO) << "Virtual device booted successfully";
-    state_ |= kGuestBootCompleted;
-  } else if (read_result->event == monitor::Event::BootFailed) {
-    LOG(ERROR) << "Virtual device failed to boot";
-    state_ |= kGuestBootFailed;
-  }  // Ignore the other signals
-
-  return MaybeWriteNotification();
-}
-
-bool CvdBootStateMachine::BootCompleted() const {
-  return state_ & kGuestBootCompleted;
-}
-
-bool CvdBootStateMachine::BootFailed() const {
-  return state_ & kGuestBootFailed;
-}
-
-void CvdBootStateMachine::SendExitCode(RunnerExitCodes exit_code, SharedFD fd) {
-  fd->Write(&exit_code, sizeof(exit_code));
-  // The foreground process will exit after receiving the exit code, if we try
-  // to write again we'll get a SIGPIPE
-  fd->Close();
-}
-
-bool CvdBootStateMachine::MaybeWriteNotification() {
-  std::vector<SharedFD> fds = {reboot_notification_, fg_launcher_pipe_};
-  for (auto& fd : fds) {
-    if (fd->IsOpen()) {
-      if (BootCompleted()) {
-        SendExitCode(RunnerExitCodes::kSuccess, fd);
-      } else if (state_ & kGuestBootFailed) {
-        SendExitCode(RunnerExitCodes::kVirtualDeviceBootFailed, fd);
+  void SendExitCode(RunnerExitCodes exit_code, SharedFD fd) {
+    fd->Write(&exit_code, sizeof(exit_code));
+    // The foreground process will exit after receiving the exit code, if we try
+    // to write again we'll get a SIGPIPE
+    fd->Close();
+  }
+  bool MaybeWriteNotification() {
+    std::vector<SharedFD> fds = {reboot_notification_, fg_launcher_pipe_};
+    for (auto& fd : fds) {
+      if (fd->IsOpen()) {
+        if (BootCompleted()) {
+          SendExitCode(RunnerExitCodes::kSuccess, fd);
+        } else if (state_ & kGuestBootFailed) {
+          SendExitCode(RunnerExitCodes::kVirtualDeviceBootFailed, fd);
+        }
       }
     }
+    // Either we sent the code before or just sent it, in any case the state is
+    // final
+    return BootCompleted() || (state_ & kGuestBootFailed);
   }
-  // Either we sent the code before or just sent it, in any case the state is
-  // final
-  return BootCompleted() || (state_ & kGuestBootFailed);
+
+  ProcessLeader& process_leader_;
+  KernelLogPipeProvider& kernel_log_pipe_provider_;
+
+  std::thread boot_event_handler_;
+  SharedFD fg_launcher_pipe_;
+  SharedFD reboot_notification_;
+  int state_;
+  static const int kBootStarted = 0;
+  static const int kGuestBootCompleted = 1 << 0;
+  static const int kGuestBootFailed = 1 << 1;
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider>>
+bootStateMachineComponent() {
+  return fruit::createComponent()
+      .addMultibinding<Feature, ProcessLeader>()
+      .addMultibinding<Feature, CvdBootStateMachine>();
 }
 
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/boot_state_machine.h b/host/commands/run_cvd/boot_state_machine.h
index 0d02e72..796bb6f 100644
--- a/host/commands/run_cvd/boot_state_machine.h
+++ b/host/commands/run_cvd/boot_state_machine.h
@@ -15,39 +15,13 @@
  */
 #pragma once
 
-#include <memory>
-#include <thread>
-
-#include "common/libs/fs/shared_fd.h"
-#include "host/commands/run_cvd/runner_defs.h"
+#include "host/commands/run_cvd/launch.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
 
 namespace cuttlefish {
 
-// Maintains the state of the boot process, once a final state is reached
-// (success or failure) it sends the appropriate exit code to the foreground
-// launcher process
-class CvdBootStateMachine {
- public:
-  CvdBootStateMachine(SharedFD fg_launcher_pipe, SharedFD reboot_notification,
-                      SharedFD boot_events_pipe);
-  ~CvdBootStateMachine();
-
- private:
-  // Returns true if the machine is left in a final state
-  bool OnBootEvtReceived(SharedFD boot_events_pipe);
-  bool BootCompleted() const;
-  bool BootFailed() const;
-
-  void SendExitCode(RunnerExitCodes exit_code, SharedFD fd);
-  bool MaybeWriteNotification();
-
-  std::thread boot_event_handler_;
-  SharedFD fg_launcher_pipe_;
-  SharedFD reboot_notification_;
-  int state_;
-  static const int kBootStarted = 0;
-  static const int kGuestBootCompleted = 1 << 0;
-  static const int kGuestBootFailed = 1 << 1;
-};
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider>>
+bootStateMachineComponent();
 
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch.cc b/host/commands/run_cvd/launch.cc
index 3037e6b..78166e4 100644
--- a/host/commands/run_cvd/launch.cc
+++ b/host/commands/run_cvd/launch.cc
@@ -1,748 +1,667 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 #include "host/commands/run_cvd/launch.h"
 
-#include <sys/stat.h>
-#include <sys/types.h>
-
 #include <android-base/logging.h>
-#include <android-base/strings.h>
+#include <unordered_set>
+#include <utility>
+#include <vector>
 
-#include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/files.h"
-#include "common/libs/utils/size_utils.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/run_cvd/process_monitor.h"
+#include "host/commands/run_cvd/reporting.h"
 #include "host/commands/run_cvd/runner_defs.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/inject.h"
 #include "host/libs/config/known_paths.h"
-#include "host/libs/vm_manager/crosvm_manager.h"
-#include "host/libs/vm_manager/qemu_manager.h"
 
 namespace cuttlefish {
 
-using vm_manager::QemuManager;
-
 namespace {
 
-std::string GetAdbConnectorTcpArg(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  return std::string{"0.0.0.0:"} + std::to_string(instance.host_port());
-}
-
-std::string GetAdbConnectorVsockArg(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  return std::string{"vsock:"} + std::to_string(instance.vsock_guest_cid()) +
-         std::string{":5555"};
-}
-
-bool AdbModeEnabled(const CuttlefishConfig& config, AdbMode mode) {
-  return config.adb_mode().count(mode) > 0;
-}
-
-bool AdbVsockTunnelEnabled(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  return instance.vsock_guest_cid() > 2 &&
-         AdbModeEnabled(config, AdbMode::VsockTunnel);
-}
-
-bool AdbVsockHalfTunnelEnabled(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  return instance.vsock_guest_cid() > 2 &&
-         AdbModeEnabled(config, AdbMode::VsockHalfTunnel);
-}
-
-bool AdbTcpConnectorEnabled(const CuttlefishConfig& config) {
-  bool vsock_tunnel = AdbVsockTunnelEnabled(config);
-  bool vsock_half_tunnel = AdbVsockHalfTunnelEnabled(config);
-  return config.run_adb_connector() && (vsock_tunnel || vsock_half_tunnel);
-}
-
-bool AdbVsockConnectorEnabled(const CuttlefishConfig& config) {
-  return config.run_adb_connector() &&
-         AdbModeEnabled(config, AdbMode::NativeVsock);
-}
-
-SharedFD CreateUnixInputServer(const std::string& path) {
-  auto server =
-      SharedFD::SocketLocalServer(path.c_str(), false, SOCK_STREAM, 0666);
-  if (!server->IsOpen()) {
-    LOG(ERROR) << "Unable to create unix input server: " << server->StrError();
-    return {};
-  }
-  return server;
-}
-
-// Creates the frame and input sockets and add the relevant arguments to the vnc
-// server and webrtc commands
-void CreateStreamerServers(Command* cmd, const CuttlefishConfig& config) {
-  SharedFD touch_server;
-  SharedFD keyboard_server;
-
-  auto instance = config.ForDefaultInstance();
-  if (config.vm_manager() == QemuManager::name()) {
-    cmd->AddParameter("-write_virtio_input");
-
-    touch_server = SharedFD::VsockServer(instance.touch_server_port(),
-                                         SOCK_STREAM);
-    keyboard_server = SharedFD::VsockServer(instance.keyboard_server_port(),
-                                            SOCK_STREAM);
-  } else {
-    touch_server = CreateUnixInputServer(instance.touch_socket_path());
-    keyboard_server = CreateUnixInputServer(instance.keyboard_socket_path());
-  }
-  if (!touch_server->IsOpen()) {
-    LOG(ERROR) << "Could not open touch server: " << touch_server->StrError();
-    return;
-  }
-  cmd->AddParameter("-touch_fd=", touch_server);
-
-  if (!keyboard_server->IsOpen()) {
-    LOG(ERROR) << "Could not open keyboard server: "
-               << keyboard_server->StrError();
-    return;
-  }
-  cmd->AddParameter("-keyboard_fd=", keyboard_server);
-
-  if (config.enable_webrtc() &&
-      config.vm_manager() == vm_manager::CrosvmManager::name()) {
-    SharedFD switches_server =
-        CreateUnixInputServer(instance.switches_socket_path());
-    if (!switches_server->IsOpen()) {
-      LOG(ERROR) << "Could not open switches server: "
-                 << switches_server->StrError();
-      return;
-    }
-    cmd->AddParameter("-switches_fd=", switches_server);
-  }
-
-  SharedFD frames_server = CreateUnixInputServer(instance.frames_socket_path());
-  if (!frames_server->IsOpen()) {
-    LOG(ERROR) << "Could not open frames server: " << frames_server->StrError();
-    return;
-  }
-  cmd->AddParameter("-frame_server_fd=", frames_server);
-
-  if (config.enable_audio()) {
-    auto path = config.ForDefaultInstance().audio_server_path();
-    auto audio_server =
-      SharedFD::SocketLocalServer(path.c_str(), false, SOCK_SEQPACKET, 0666);
-    if (!audio_server->IsOpen()) {
-      LOG(ERROR) << "Could not create audio server: " << audio_server->StrError();
-      return;
-    }
-    cmd->AddParameter("--audio_server_fd=", audio_server);
-  }
+template <typename T>
+std::vector<T> single_element_emplace(T&& element) {
+  std::vector<T> vec;
+  vec.emplace_back(std::move(element));
+  return vec;
 }
 
 }  // namespace
 
-std::vector<SharedFD> LaunchKernelLogMonitor(
-    const CuttlefishConfig& config, ProcessMonitor* process_monitor,
-    unsigned int number_of_event_pipes) {
-  auto instance = config.ForDefaultInstance();
-  auto log_name = instance.kernel_log_pipe_name();
-  if (mkfifo(log_name.c_str(), 0600) != 0) {
-    LOG(ERROR) << "Unable to create named pipe at " << log_name << ": "
-               << strerror(errno);
-    return {};
+CommandSource::~CommandSource() = default;
+
+KernelLogPipeProvider::~KernelLogPipeProvider() = default;
+
+class KernelLogMonitor : public CommandSource,
+                         public KernelLogPipeProvider,
+                         public DiagnosticInformation {
+ public:
+  INJECT(KernelLogMonitor(const CuttlefishConfig::InstanceSpecific& instance))
+      : instance_(instance) {}
+
+  // DiagnosticInformation
+  std::vector<std::string> Diagnostics() const override {
+    return {"Kernel log: " + instance_.PerInstancePath("kernel.log")};
   }
 
-  SharedFD pipe;
-  // Open the pipe here (from the launcher) to ensure the pipe is not deleted
-  // due to the usage counters in the kernel reaching zero. If this is not done
-  // and the kernel_log_monitor crashes for some reason the VMM may get SIGPIPE.
-  pipe = SharedFD::Open(log_name.c_str(), O_RDWR);
-  Command command(KernelLogMonitorBinary());
-  command.AddParameter("-log_pipe_fd=", pipe);
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command command(KernelLogMonitorBinary());
+    command.AddParameter("-log_pipe_fd=", fifo_);
 
-  std::vector<SharedFD> ret;
-
-  if (number_of_event_pipes > 0) {
-    command.AddParameter("-subscriber_fds=");
-    for (unsigned int i = 0; i < number_of_event_pipes; ++i) {
-      SharedFD event_pipe_write_end, event_pipe_read_end;
-      if (!SharedFD::Pipe(&event_pipe_read_end, &event_pipe_write_end)) {
-        LOG(ERROR) << "Unable to create kernel log events pipe: " << strerror(errno);
-        std::exit(RunnerExitCodes::kPipeIOError);
-      }
-      if (i > 0) {
-        command.AppendToLastParameter(",");
-      }
-      command.AppendToLastParameter(event_pipe_write_end);
-      ret.push_back(event_pipe_read_end);
-    }
-  }
-
-  process_monitor->AddCommand(std::move(command));
-
-  return ret;
-}
-
-void LaunchRootCanal(const CuttlefishConfig& config,
-                     ProcessMonitor* process_monitor) {
-  if (!config.enable_host_bluetooth()) {
-    return;
-  }
-
-  auto instance = config.ForDefaultInstance();
-  Command command(RootCanalBinary());
-
-  // Test port
-  command.AddParameter(instance.rootcanal_test_port());
-  // HCI server port
-  command.AddParameter(instance.rootcanal_hci_port());
-  // Link server port
-  command.AddParameter(instance.rootcanal_link_port());
-  // Bluetooth controller properties file
-  command.AddParameter("--controller_properties_file=",
-                       instance.rootcanal_config_file());
-  // Default commands file
-  command.AddParameter("--default_commands_file=",
-                       instance.rootcanal_default_commands_file());
-
-  process_monitor->AddCommand(std::move(command));
-  return;
-}
-
-void LaunchLogcatReceiver(const CuttlefishConfig& config,
-                          ProcessMonitor* process_monitor) {
-  auto instance = config.ForDefaultInstance();
-  auto log_name = instance.logcat_pipe_name();
-  if (mkfifo(log_name.c_str(), 0600) != 0) {
-    LOG(ERROR) << "Unable to create named pipe at " << log_name << ": "
-               << strerror(errno);
-    return;
-  }
-
-  SharedFD pipe;
-  // Open the pipe here (from the launcher) to ensure the pipe is not deleted
-  // due to the usage counters in the kernel reaching zero. If this is not done
-  // and the logcat_receiver crashes for some reason the VMM may get SIGPIPE.
-  pipe = SharedFD::Open(log_name.c_str(), O_RDWR);
-  Command command(LogcatReceiverBinary());
-  command.AddParameter("-log_pipe_fd=", pipe);
-
-  process_monitor->AddCommand(std::move(command));
-  return;
-}
-
-void LaunchConfigServer(const CuttlefishConfig& config,
-                        ProcessMonitor* process_monitor) {
-  auto instance = config.ForDefaultInstance();
-  auto port = instance.config_server_port();
-  auto socket = SharedFD::VsockServer(port, SOCK_STREAM);
-  if (!socket->IsOpen()) {
-    LOG(ERROR) << "Unable to create configuration server socket: "
-               << socket->StrError();
-    std::exit(RunnerExitCodes::kConfigServerError);
-  }
-  Command cmd(ConfigServerBinary());
-  cmd.AddParameter("-server_fd=", socket);
-  process_monitor->AddCommand(std::move(cmd));
-  return;
-}
-
-void LaunchTombstoneReceiver(const CuttlefishConfig& config,
-                             ProcessMonitor* process_monitor) {
-  auto instance = config.ForDefaultInstance();
-
-  std::string tombstoneDir = instance.PerInstancePath("tombstones");
-  if (!DirectoryExists(tombstoneDir.c_str())) {
-    LOG(DEBUG) << "Setting up " << tombstoneDir;
-    if (mkdir(tombstoneDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) <
-        0) {
-      LOG(ERROR) << "Failed to create tombstone directory: " << tombstoneDir
-                 << ". Error: " << errno;
-      exit(RunnerExitCodes::kTombstoneDirCreationError);
-      return;
-    }
-  }
-
-  auto port = instance.tombstone_receiver_port();
-  auto socket = SharedFD::VsockServer(port, SOCK_STREAM);
-  if (!socket->IsOpen()) {
-    LOG(ERROR) << "Unable to create tombstone server socket: "
-               << socket->StrError();
-    std::exit(RunnerExitCodes::kTombstoneServerError);
-    return;
-  }
-  Command cmd(TombstoneReceiverBinary());
-  cmd.AddParameter("-server_fd=", socket);
-  cmd.AddParameter("-tombstone_dir=", tombstoneDir);
-
-  process_monitor->AddCommand(std::move(cmd));
-  return;
-}
-
-void LaunchVNCServer(const CuttlefishConfig& config,
-                     ProcessMonitor* process_monitor) {
-  auto instance = config.ForDefaultInstance();
-  // Launch the vnc server, don't wait for it to complete
-  auto port_options = "-port=" + std::to_string(instance.vnc_server_port());
-  Command vnc_server(VncServerBinary());
-  vnc_server.AddParameter(port_options);
-
-  CreateStreamerServers(&vnc_server, config);
-
-  process_monitor->AddCommand(std::move(vnc_server));
-}
-
-void LaunchAdbConnectorIfEnabled(ProcessMonitor* process_monitor,
-                                 const CuttlefishConfig& config) {
-  Command adb_connector(AdbConnectorBinary());
-  std::set<std::string> addresses;
-
-  if (AdbTcpConnectorEnabled(config)) {
-    addresses.insert(GetAdbConnectorTcpArg(config));
-  }
-  if (AdbVsockConnectorEnabled(config)) {
-    addresses.insert(GetAdbConnectorVsockArg(config));
-  }
-
-  if (addresses.size() > 0) {
-    std::string address_arg = "--addresses=";
-    for (auto& arg : addresses) {
-      address_arg += arg + ",";
-    }
-    address_arg.pop_back();
-    adb_connector.AddParameter(address_arg);
-    process_monitor->AddCommand(std::move(adb_connector));
-  }
-}
-
-void LaunchWebRTC(ProcessMonitor* process_monitor,
-                  const CuttlefishConfig& config,
-                  SharedFD kernel_log_events_pipe) {
-  if (config.ForDefaultInstance().start_webrtc_sig_server()) {
-    Command sig_server(WebRtcSigServerBinary());
-    sig_server.AddParameter("-assets_dir=", config.webrtc_assets_dir());
-    if (!config.webrtc_certs_dir().empty()) {
-      sig_server.AddParameter("-certs_dir=", config.webrtc_certs_dir());
-    }
-    sig_server.AddParameter("-http_server_port=", config.sig_server_port());
-    process_monitor->AddCommand(std::move(sig_server));
-  }
-
-  // Currently there is no way to ensure the signaling server will already have
-  // bound the socket to the port by the time the webrtc process runs (the
-  // common technique of doing it from the launcher is not possible here as the
-  // server library being used creates its own sockets). However, this issue is
-  // mitigated slightly by doing some retrying and backoff in the webrtc process
-  // when connecting to the websocket, so it shouldn't be an issue most of the
-  // time.
-  cuttlefish::SharedFD client_socket;
-  cuttlefish::SharedFD host_socket;
-  CHECK(cuttlefish::SharedFD::SocketPair(AF_LOCAL, SOCK_STREAM, 0,
-                                         &client_socket, &host_socket))
-      << "Could not open command socket for webRTC";
-
-  auto stopper = [host_socket = std::move(host_socket)](cuttlefish::Subprocess* proc) {
-    struct timeval timeout;
-    timeout.tv_sec = 3;
-    timeout.tv_usec = 0;
-    CHECK(host_socket->SetSockOpt(SOL_SOCKET, SO_RCVTIMEO, &timeout,
-                                  sizeof(timeout)) == 0)
-        << "Could not set receive timeout";
-
-    cuttlefish::WriteAll(host_socket, "C");
-    char response[1];
-    int read_ret = host_socket->Read(response, sizeof(response));
-    if (read_ret != 0) {
-      LOG(ERROR) << "Failed to read response from webrtc";
-    }
-    cuttlefish::KillSubprocess(proc);
-    return true;
-  };
-
-  cuttlefish::Command webrtc(cuttlefish::WebRtcBinary(),
-                             cuttlefish::SubprocessStopper(stopper));
-
-  webrtc.UnsetFromEnvironment({"http_proxy"});
-
-  CreateStreamerServers(&webrtc, config);
-
-  webrtc.AddParameter("--command_fd=", client_socket);
-
-  webrtc.AddParameter("-kernel_log_events_fd=", kernel_log_events_pipe);
-
-  LaunchCustomActionServers(webrtc, process_monitor, config);
-
-  // TODO get from launcher params
-  process_monitor->AddCommand(std::move(webrtc));
-}
-
-bool StopModemSimulator() {
-  auto config = CuttlefishConfig::Get();
-  auto instance = config->ForDefaultInstance();
-
-  std::string monitor_socket_name = "modem_simulator";
-  std::stringstream ss;
-  ss << instance.host_port();
-  monitor_socket_name.append(ss.str());
-  auto monitor_sock = SharedFD::SocketLocalClient(
-      monitor_socket_name.c_str(), true, SOCK_STREAM);
-  if (!monitor_sock->IsOpen()) {
-    LOG(ERROR) << "The connection to modem simulator is closed";
-    return false;
-  }
-  std::string msg("STOP");
-  if (monitor_sock->Write(msg.data(), msg.size()) < 0) {
-    monitor_sock->Close();
-    LOG(ERROR) << "Failed to send 'STOP' to modem simulator";
-    return false;
-  }
-  char buf[64] = {0};
-  if (monitor_sock->Read(buf, sizeof(buf)) <= 0) {
-    monitor_sock->Close();
-    LOG(ERROR) << "Failed to read message from modem simulator";
-    return false;
-  }
-  if (strcmp(buf, "OK")) {
-    monitor_sock->Close();
-    LOG(ERROR) << "Read '" << buf << "' instead of 'OK' from modem simulator";
-    return false;
-  }
-
-  return true;
-}
-
-void LaunchModemSimulatorIfEnabled(
-    const CuttlefishConfig& config,
-    ProcessMonitor* process_monitor) {
-  if (!config.enable_modem_simulator()) {
-    LOG(DEBUG) << "Modem simulator not enabled";
-    return;
-  }
-
-  int instance_number = config.modem_simulator_instance_number();
-  if (instance_number > 3 /* max value */ || instance_number < 0) {
-    LOG(ERROR)
-        << "Modem simulator instance number should range between 1 and 3";
-    return;
-  }
-
-  Command cmd(
-      ModemSimulatorBinary(), [](Subprocess* proc) {
-        auto stopped = StopModemSimulator();
-        if (stopped) {
-          return true;
+    if (!event_pipe_write_ends_.empty()) {
+      command.AddParameter("-subscriber_fds=");
+      for (size_t i = 0; i < event_pipe_write_ends_.size(); i++) {
+        if (i > 0) {
+          command.AppendToLastParameter(",");
         }
-        LOG(WARNING) << "Failed to stop modem simulator nicely, "
-                     << "attempting to KILL";
-        return KillSubprocess(proc);
-      });
-
-  auto sim_type = config.modem_simulator_sim_type();
-  cmd.AddParameter(std::string{"-sim_type="} + std::to_string(sim_type));
-
-  auto instance = config.ForDefaultInstance();
-  auto ports = instance.modem_simulator_ports();
-  cmd.AddParameter("-server_fds=");
-  for (int i = 0; i < instance_number; ++i) {
-    auto pos = ports.find(',');
-    auto temp = (pos != std::string::npos) ? ports.substr(0, pos - 1) : ports;
-    auto port = std::stoi(temp);
-    ports = ports.substr(pos + 1);
-
-    auto socket = SharedFD::VsockServer(port, SOCK_STREAM);
-    if (!socket->IsOpen()) {
-      LOG(ERROR) << "Unable to create modem simulator server socket: "
-                 << socket->StrError();
-      std::exit(RunnerExitCodes::kModemSimulatorServerError);
-    }
-    if (i > 0) {
-      cmd.AppendToLastParameter(",");
-    }
-    cmd.AppendToLastParameter(socket);
-  }
-
-  process_monitor->AddCommand(std::move(cmd));
-}
-
-void LaunchSocketVsockProxyIfEnabled(ProcessMonitor* process_monitor,
-                                     const CuttlefishConfig& config,
-                                     SharedFD adbd_events_pipe) {
-  auto instance = config.ForDefaultInstance();
-  auto append = [](const std::string& s, const int i) -> std::string {
-    return s + std::to_string(i);
-  };
-  if (AdbVsockTunnelEnabled(config)) {
-    Command adb_tunnel(SocketVsockProxyBinary());
-    adb_tunnel.AddParameter("-adbd_events_fd=", adbd_events_pipe);
-    /**
-     * This socket_vsock_proxy (a.k.a. sv proxy) runs on the host. It assumes that
-     * another sv proxy runs inside the guest. see: shared/config/init.vendor.rc
-     * The sv proxy in the guest exposes vsock:cid:6520 across the cuttlefish instances
-     * in multi-tenancy. cid is different per instance.
-     *
-     * This host sv proxy should cooperate with the guest sv proxy. Thus, one end of
-     * the tunnel is vsock:cid:6520 regardless of instance number. Another end faces
-     * the host adb daemon via tcp. Thus, the server type is tcp here. The tcp port
-     * differs from instance to instance, and is instance.host_port()
-     *
-     */
-    adb_tunnel.AddParameter("--server=tcp");
-    adb_tunnel.AddParameter("--vsock_port=6520");
-    adb_tunnel.AddParameter(std::string{"--tcp_port="} +
-                            std::to_string(instance.host_port()));
-    adb_tunnel.AddParameter(std::string{"--vsock_cid="} +
-                            std::to_string(instance.vsock_guest_cid()));
-    process_monitor->AddCommand(std::move(adb_tunnel));
-  }
-  if (AdbVsockHalfTunnelEnabled(config)) {
-    Command adb_tunnel(SocketVsockProxyBinary());
-    adb_tunnel.AddParameter("-adbd_events_fd=", adbd_events_pipe);
-    /*
-     * This socket_vsock_proxy (a.k.a. sv proxy) runs on the host, and cooperates with
-     * the adbd inside the guest. See this file:
-     *  shared/device.mk, especially the line says "persist.adb.tcp.port="
-     *
-     * The guest adbd is listening on vsock:cid:5555 across cuttlefish instances.
-     * Sv proxy faces the host adb daemon via tcp. The server type should be therefore
-     * tcp, and the port should differ from instance to instance and be equal to
-     * instance.host_port()
-     */
-    adb_tunnel.AddParameter("--server=tcp");
-    adb_tunnel.AddParameter(append("--vsock_port=", 5555));
-    adb_tunnel.AddParameter(append("--tcp_port=", instance.host_port()));
-    adb_tunnel.AddParameter(append("--vsock_cid=", instance.vsock_guest_cid()));
-    process_monitor->AddCommand(std::move(adb_tunnel));
-  }
-}
-
-void LaunchMetrics(ProcessMonitor* process_monitor) {
-  Command metrics(MetricsBinary());
-
-  process_monitor->AddCommand(std::move(metrics));
-}
-
-void LaunchGnssGrpcProxyServerIfEnabled(const CuttlefishConfig& config,
-                                        ProcessMonitor* process_monitor) {
-    if (!config.enable_gnss_grpc_proxy() ||
-        !FileExists(GnssGrpcProxyBinary())) {
-        return;
+        command.AppendToLastParameter(event_pipe_write_ends_[i]);
+      }
     }
 
+    return single_element_emplace(std::move(command));
+  }
+
+  // KernelLogPipeProvider
+  SharedFD KernelLogPipe() override {
+    CHECK(!event_pipe_read_ends_.empty()) << "No more kernel pipes left";
+    SharedFD ret = event_pipe_read_ends_.back();
+    event_pipe_read_ends_.pop_back();
+    return ret;
+  }
+
+  // Feature
+  bool Enabled() const override { return true; }
+  std::string Name() const override { return "KernelLogMonitor"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override {
+    auto log_name = instance_.kernel_log_pipe_name();
+    if (mkfifo(log_name.c_str(), 0600) != 0) {
+      LOG(ERROR) << "Unable to create named pipe at " << log_name << ": "
+                 << strerror(errno);
+      return false;
+    }
+
+    // Open the pipe here (from the launcher) to ensure the pipe is not deleted
+    // due to the usage counters in the kernel reaching zero. If this is not
+    // done and the kernel_log_monitor crashes for some reason the VMM may get
+    // SIGPIPE.
+    fifo_ = SharedFD::Open(log_name, O_RDWR);
+    if (!fifo_->IsOpen()) {
+      LOG(ERROR) << "Unable to open \"" << log_name << "\"";
+      return false;
+    }
+
+    // TODO(schuffelen): Find a way to calculate this dynamically.
+    int number_of_event_pipes = 4;
+    if (number_of_event_pipes > 0) {
+      for (unsigned int i = 0; i < number_of_event_pipes; ++i) {
+        SharedFD event_pipe_write_end, event_pipe_read_end;
+        if (!SharedFD::Pipe(&event_pipe_read_end, &event_pipe_write_end)) {
+          PLOG(ERROR) << "Unable to create kernel log events pipe: ";
+          return false;
+        }
+        event_pipe_write_ends_.push_back(event_pipe_write_end);
+        event_pipe_read_ends_.push_back(event_pipe_read_end);
+      }
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD fifo_;
+  std::vector<SharedFD> event_pipe_write_ends_;
+  std::vector<SharedFD> event_pipe_read_ends_;
+};
+
+class RootCanal : public CommandSource {
+ public:
+  INJECT(RootCanal(const CuttlefishConfig& config,
+                   const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    if (!Enabled()) {
+      return {};
+    }
+    Command command(RootCanalBinary());
+
+    // Test port
+    command.AddParameter(instance_.rootcanal_test_port());
+    // HCI server port
+    command.AddParameter(instance_.rootcanal_hci_port());
+    // Link server port
+    command.AddParameter(instance_.rootcanal_link_port());
+    // Bluetooth controller properties file
+    command.AddParameter("--controller_properties_file=",
+                         instance_.rootcanal_config_file());
+    // Default commands file
+    command.AddParameter("--default_commands_file=",
+                         instance_.rootcanal_default_commands_file());
+
+    return single_element_emplace(std::move(command));
+  }
+
+  // Feature
+  bool Enabled() const override { return config_.enable_host_bluetooth(); }
+  std::string Name() const override { return "RootCanal"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override { return true; }
+
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class LogcatReceiver : public CommandSource, public DiagnosticInformation {
+ public:
+  INJECT(LogcatReceiver(const CuttlefishConfig::InstanceSpecific& instance))
+      : instance_(instance) {}
+  // DiagnosticInformation
+  std::vector<std::string> Diagnostics() const override {
+    return {"Logcat output: " + instance_.logcat_path()};
+  }
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command command(LogcatReceiverBinary());
+    command.AddParameter("-log_pipe_fd=", pipe_);
+    return single_element_emplace(std::move(command));
+  }
+
+  // Feature
+  bool Enabled() const override { return true; }
+  std::string Name() const override { return "LogcatReceiver"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override {
+    auto log_name = instance_.logcat_pipe_name();
+    if (mkfifo(log_name.c_str(), 0600) != 0) {
+      LOG(ERROR) << "Unable to create named pipe at " << log_name << ": "
+                 << strerror(errno);
+      return false;
+    }
+    // Open the pipe here (from the launcher) to ensure the pipe is not deleted
+    // due to the usage counters in the kernel reaching zero. If this is not
+    // done and the logcat_receiver crashes for some reason the VMM may get
+    // SIGPIPE.
+    pipe_ = SharedFD::Open(log_name.c_str(), O_RDWR);
+    if (!pipe_->IsOpen()) {
+      LOG(ERROR) << "Can't open \"" << log_name << "\": " << pipe_->StrError();
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD pipe_;
+};
+
+class ConfigServer : public CommandSource {
+ public:
+  INJECT(ConfigServer(const CuttlefishConfig::InstanceSpecific& instance))
+      : instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command cmd(ConfigServerBinary());
+    cmd.AddParameter("-server_fd=", socket_);
+    return single_element_emplace(std::move(cmd));
+  }
+
+  // Feature
+  bool Enabled() const override { return true; }
+  std::string Name() const override { return "ConfigServer"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override {
+    auto port = instance_.config_server_port();
+    socket_ = SharedFD::VsockServer(port, SOCK_STREAM);
+    if (!socket_->IsOpen()) {
+      LOG(ERROR) << "Unable to create configuration server socket: "
+                 << socket_->StrError();
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD socket_;
+};
+
+class TombstoneReceiver : public CommandSource {
+ public:
+  INJECT(TombstoneReceiver(const CuttlefishConfig::InstanceSpecific& instance))
+      : instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command cmd(TombstoneReceiverBinary());
+    cmd.AddParameter("-server_fd=", socket_);
+    cmd.AddParameter("-tombstone_dir=", tombstone_dir_);
+    return single_element_emplace(std::move(cmd));
+  }
+
+  // Feature
+  bool Enabled() const override { return true; }
+  std::string Name() const override { return "TombstoneReceiver"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override {
+    tombstone_dir_ = instance_.PerInstancePath("tombstones");
+    if (!DirectoryExists(tombstone_dir_.c_str())) {
+      LOG(DEBUG) << "Setting up " << tombstone_dir_;
+      if (mkdir(tombstone_dir_.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) <
+          0) {
+        LOG(ERROR) << "Failed to create tombstone directory: " << tombstone_dir_
+                   << ". Error: " << errno;
+        return false;
+      }
+    }
+
+    auto port = instance_.tombstone_receiver_port();
+    socket_ = SharedFD::VsockServer(port, SOCK_STREAM);
+    if (!socket_->IsOpen()) {
+      LOG(ERROR) << "Unable to create tombstone server socket: "
+                 << socket_->StrError();
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD socket_;
+  std::string tombstone_dir_;
+};
+
+class MetricsService : public CommandSource {
+ public:
+  INJECT(MetricsService(const CuttlefishConfig& config)) : config_(config) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    return single_element_emplace(Command(MetricsBinary()));
+  }
+
+  // Feature
+  bool Enabled() const override {
+    return config_.enable_metrics() == CuttlefishConfig::kYes;
+  }
+  std::string Name() const override { return "MetricsService"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override { return true; }
+
+ private:
+  const CuttlefishConfig& config_;
+};
+
+class GnssGrpcProxyServer : public CommandSource {
+ public:
+  INJECT(
+      GnssGrpcProxyServer(const CuttlefishConfig& config,
+                          const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
     Command gnss_grpc_proxy_cmd(GnssGrpcProxyBinary());
-    auto instance = config.ForDefaultInstance();
+    const unsigned gnss_grpc_proxy_server_port =
+        instance_.gnss_grpc_proxy_server_port();
+    gnss_grpc_proxy_cmd.AddParameter("--gnss_in_fd=", gnss_grpc_proxy_in_wr_);
+    gnss_grpc_proxy_cmd.AddParameter("--gnss_out_fd=", gnss_grpc_proxy_out_rd_);
+    gnss_grpc_proxy_cmd.AddParameter("--gnss_grpc_port=",
+                                     gnss_grpc_proxy_server_port);
+    if (!instance_.gnss_file_path().empty()) {
+      // If path is provided, proxy will start as local mode.
+      gnss_grpc_proxy_cmd.AddParameter("--gnss_file_path=",
+                                       instance_.gnss_file_path());
+    }
+    return single_element_emplace(std::move(gnss_grpc_proxy_cmd));
+  }
 
-    auto gnss_in_pipe_name = instance.gnss_in_pipe_name();
+  // Feature
+  bool Enabled() const override {
+    return config_.enable_gnss_grpc_proxy() &&
+           FileExists(GnssGrpcProxyBinary());
+  }
+
+  std::string Name() const override { return "GnssGrpcProxyServer"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override {
+    auto gnss_in_pipe_name = instance_.gnss_in_pipe_name();
     if (mkfifo(gnss_in_pipe_name.c_str(), 0600) != 0) {
       auto error = errno;
       LOG(ERROR) << "Failed to create gnss input fifo for crosvm: "
-                << strerror(error);
-      return;
+                 << strerror(error);
+      return false;
     }
 
-    auto gnss_out_pipe_name = instance.gnss_out_pipe_name();
+    auto gnss_out_pipe_name = instance_.gnss_out_pipe_name();
     if (mkfifo(gnss_out_pipe_name.c_str(), 0660) != 0) {
       auto error = errno;
       LOG(ERROR) << "Failed to create gnss output fifo for crosvm: "
-                << strerror(error);
-      return;
+                 << strerror(error);
+      return false;
     }
 
     // These fds will only be read from or written to, but open them with
     // read and write access to keep them open in case the subprocesses exit
-    SharedFD gnss_grpc_proxy_in_wr =
-        SharedFD::Open(gnss_in_pipe_name.c_str(), O_RDWR);
-    if (!gnss_grpc_proxy_in_wr->IsOpen()) {
+    gnss_grpc_proxy_in_wr_ = SharedFD::Open(gnss_in_pipe_name.c_str(), O_RDWR);
+    if (!gnss_grpc_proxy_in_wr_->IsOpen()) {
       LOG(ERROR) << "Failed to open gnss_grpc_proxy input fifo for writes: "
-                << gnss_grpc_proxy_in_wr->StrError();
-      return;
+                 << gnss_grpc_proxy_in_wr_->StrError();
+      return false;
     }
 
-    SharedFD gnss_grpc_proxy_out_rd =
+    gnss_grpc_proxy_out_rd_ =
         SharedFD::Open(gnss_out_pipe_name.c_str(), O_RDWR);
-    if (!gnss_grpc_proxy_out_rd->IsOpen()) {
+    if (!gnss_grpc_proxy_out_rd_->IsOpen()) {
       LOG(ERROR) << "Failed to open gnss_grpc_proxy output fifo for reads: "
-                << gnss_grpc_proxy_out_rd->StrError();
-      return;
+                 << gnss_grpc_proxy_out_rd_->StrError();
+      return false;
     }
-
-    const unsigned gnss_grpc_proxy_server_port = instance.gnss_grpc_proxy_server_port();
-    gnss_grpc_proxy_cmd.AddParameter("--gnss_in_fd=", gnss_grpc_proxy_in_wr);
-    gnss_grpc_proxy_cmd.AddParameter("--gnss_out_fd=", gnss_grpc_proxy_out_rd);
-    gnss_grpc_proxy_cmd.AddParameter("--gnss_grpc_port=", gnss_grpc_proxy_server_port);
-    if (!instance.gnss_file_path().empty()) {
-      // If path is provided, proxy will start as local mode.
-      gnss_grpc_proxy_cmd.AddParameter("--gnss_file_path=", instance.gnss_file_path());
-    }
-    process_monitor->AddCommand(std::move(gnss_grpc_proxy_cmd));
-}
-
-void LaunchBluetoothConnector(ProcessMonitor* process_monitor,
-                              const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  std::vector<std::string> fifo_paths = {
-      instance.PerInstanceInternalPath("bt_fifo_vm.in"),
-      instance.PerInstanceInternalPath("bt_fifo_vm.out"),
-  };
-  std::vector<SharedFD> fifos;
-  for (const auto& path : fifo_paths) {
-    unlink(path.c_str());
-    if (mkfifo(path.c_str(), 0660) < 0) {
-      PLOG(ERROR) << "Could not create " << path;
-      return;
-    }
-    auto fd = SharedFD::Open(path, O_RDWR);
-    if (!fd->IsOpen()) {
-      LOG(ERROR) << "Could not open " << path << ": " << fd->StrError();
-      return;
-    }
-    fifos.push_back(fd);
+    return true;
   }
 
-  Command command(DefaultHostArtifactsPath("bin/bt_connector"));
-  command.AddParameter("-bt_out=", fifos[0]);
-  command.AddParameter("-bt_in=", fifos[1]);
-  command.AddParameter("-hci_port=", instance.rootcanal_hci_port());
-  command.AddParameter("-link_port=", instance.rootcanal_link_port());
-  command.AddParameter("-test_port=", instance.rootcanal_test_port());
-  process_monitor->AddCommand(std::move(command));
-}
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD gnss_grpc_proxy_in_wr_;
+  SharedFD gnss_grpc_proxy_out_rd_;
+};
 
-void LaunchSecureEnvironment(ProcessMonitor* process_monitor,
-                             const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  std::vector<std::string> fifo_paths = {
-    instance.PerInstanceInternalPath("keymaster_fifo_vm.in"),
-    instance.PerInstanceInternalPath("keymaster_fifo_vm.out"),
-    instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"),
-    instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
-  };
-  std::vector<SharedFD> fifos;
-  for (const auto& path : fifo_paths) {
-    unlink(path.c_str());
-    if (mkfifo(path.c_str(), 0600) < 0) {
-      PLOG(ERROR) << "Could not create " << path;
-      return;
-    }
-    auto fd = SharedFD::Open(path, O_RDWR);
-    if (!fd->IsOpen()) {
-      LOG(ERROR) << "Could not open " << path << ": " << fd->StrError();
-      return;
-    }
-    fifos.push_back(fd);
+class BluetoothConnector : public CommandSource {
+ public:
+  INJECT(BluetoothConnector(const CuttlefishConfig& config,
+                            const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command command(DefaultHostArtifactsPath("bin/bt_connector"));
+    command.AddParameter("-bt_out=", fifos_[0]);
+    command.AddParameter("-bt_in=", fifos_[1]);
+    command.AddParameter("-hci_port=", instance_.rootcanal_hci_port());
+    command.AddParameter("-link_port=", instance_.rootcanal_link_port());
+    command.AddParameter("-test_port=", instance_.rootcanal_test_port());
+    return single_element_emplace(std::move(command));
   }
 
-  Command command(HostBinaryPath("secure_env"));
-  command.AddParameter("-keymaster_fd_out=", fifos[0]);
-  command.AddParameter("-keymaster_fd_in=", fifos[1]);
-  command.AddParameter("-gatekeeper_fd_out=", fifos[2]);
-  command.AddParameter("-gatekeeper_fd_in=", fifos[3]);
+  // Feature
+  bool Enabled() const override { return config_.enable_host_bluetooth(); }
 
-  const auto& secure_hals = config.secure_hals();
-  bool secure_keymint = secure_hals.count(SecureHal::Keymint) > 0;
-  command.AddParameter("-keymint_impl=", secure_keymint ? "tpm" : "software");
-  bool secure_gatekeeper = secure_hals.count(SecureHal::Gatekeeper) > 0;
-  auto gatekeeper_impl = secure_gatekeeper ? "tpm" : "software";
-  command.AddParameter("-gatekeeper_impl=", gatekeeper_impl);
+  std::string Name() const override { return "BluetoothConnector"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
 
-  process_monitor->AddCommand(std::move(command));
-}
-
-void LaunchCustomActionServers(Command& webrtc_cmd,
-                               ProcessMonitor* process_monitor,
-                               const CuttlefishConfig& config) {
-  bool first = true;
-  for (const auto& custom_action : config.custom_actions()) {
-    if (custom_action.server) {
-      // Create a socket pair that will be used for communication between
-      // WebRTC and the action server.
-      SharedFD webrtc_socket, action_server_socket;
-      if (!SharedFD::SocketPair(AF_LOCAL, SOCK_STREAM, 0,
-                                &webrtc_socket, &action_server_socket)) {
-        LOG(ERROR) << "Unable to create custom action server socket pair: "
-                   << strerror(errno);
-        continue;
+ protected:
+  bool Setup() override {
+    std::vector<std::string> fifo_paths = {
+        instance_.PerInstanceInternalPath("bt_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("bt_fifo_vm.out"),
+    };
+    for (const auto& path : fifo_paths) {
+      unlink(path.c_str());
+      if (mkfifo(path.c_str(), 0660) < 0) {
+        PLOG(ERROR) << "Could not create " << path;
+        return false;
       }
-
-      // Launch the action server, providing its socket pair fd as the only argument.
-      std::string binary = "bin/" + *(custom_action.server);
-      Command command(DefaultHostArtifactsPath(binary));
-      command.AddParameter(action_server_socket);
-      process_monitor->AddCommand(std::move(command));
-
-      // Pass the WebRTC socket pair fd to WebRTC.
-      if (first) {
-        first = false;
-        webrtc_cmd.AddParameter("-action_servers=", *custom_action.server, ":", webrtc_socket);
-      } else {
-        webrtc_cmd.AppendToLastParameter(",", *custom_action.server, ":", webrtc_socket);
+      auto fd = SharedFD::Open(path, O_RDWR);
+      if (!fd->IsOpen()) {
+        LOG(ERROR) << "Could not open " << path << ": " << fd->StrError();
+        return false;
       }
+      fifos_.push_back(fd);
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  std::vector<SharedFD> fifos_;
+};
+
+class SecureEnvironment : public CommandSource {
+ public:
+  INJECT(SecureEnvironment(const CuttlefishConfig& config,
+                           const CuttlefishConfig::InstanceSpecific& instance,
+                           KernelLogPipeProvider& kernel_log_pipe_provider))
+      : config_(config),
+        instance_(instance),
+        kernel_log_pipe_provider_(kernel_log_pipe_provider) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command command(HostBinaryPath("secure_env"));
+    command.AddParameter("-keymaster_fd_out=", fifos_[0]);
+    command.AddParameter("-keymaster_fd_in=", fifos_[1]);
+    command.AddParameter("-gatekeeper_fd_out=", fifos_[2]);
+    command.AddParameter("-gatekeeper_fd_in=", fifos_[3]);
+
+    const auto& secure_hals = config_.secure_hals();
+    bool secure_keymint = secure_hals.count(SecureHal::Keymint) > 0;
+    command.AddParameter("-keymint_impl=", secure_keymint ? "tpm" : "software");
+    bool secure_gatekeeper = secure_hals.count(SecureHal::Gatekeeper) > 0;
+    auto gatekeeper_impl = secure_gatekeeper ? "tpm" : "software";
+    command.AddParameter("-gatekeeper_impl=", gatekeeper_impl);
+
+    command.AddParameter("-kernel_events_fd=", kernel_log_pipe_);
+
+    return single_element_emplace(std::move(command));
+  }
+
+  // Feature
+  bool Enabled() const override { return config_.enable_host_bluetooth(); }
+  std::string Name() const override { return "SecureEnvironment"; }
+  std::unordered_set<Feature*> Dependencies() const override {
+    return {&kernel_log_pipe_provider_};
+  }
+
+ protected:
+  bool Setup() override {
+    std::vector<std::string> fifo_paths = {
+        instance_.PerInstanceInternalPath("keymaster_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("keymaster_fifo_vm.out"),
+        instance_.PerInstanceInternalPath("gatekeeper_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
+    };
+    std::vector<SharedFD> fifos;
+    for (const auto& path : fifo_paths) {
+      unlink(path.c_str());
+      if (mkfifo(path.c_str(), 0600) < 0) {
+        PLOG(ERROR) << "Could not create " << path;
+        return false;
+      }
+      auto fd = SharedFD::Open(path, O_RDWR);
+      if (!fd->IsOpen()) {
+        LOG(ERROR) << "Could not open " << path << ": " << fd->StrError();
+        return false;
+      }
+      fifos_.push_back(fd);
+    }
+
+    kernel_log_pipe_ = kernel_log_pipe_provider_.KernelLogPipe();
+
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  std::vector<SharedFD> fifos_;
+  KernelLogPipeProvider& kernel_log_pipe_provider_;
+  SharedFD kernel_log_pipe_;
+};
+
+class VehicleHalServer : public CommandSource {
+ public:
+  INJECT(VehicleHalServer(const CuttlefishConfig& config,
+                          const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command grpc_server(VehicleHalGrpcServerBinary());
+
+    const unsigned vhal_server_cid = 2;
+    const unsigned vhal_server_port = instance_.vehicle_hal_server_port();
+    const std::string vhal_server_power_state_file =
+        AbsolutePath(instance_.PerInstancePath("power_state"));
+    const std::string vhal_server_power_state_socket =
+        AbsolutePath(instance_.PerInstancePath("power_state_socket"));
+
+    grpc_server.AddParameter("--server_cid=", vhal_server_cid);
+    grpc_server.AddParameter("--server_port=", vhal_server_port);
+    grpc_server.AddParameter("--power_state_file=",
+                             vhal_server_power_state_file);
+    grpc_server.AddParameter("--power_state_socket=",
+                             vhal_server_power_state_socket);
+    return single_element_emplace(std::move(grpc_server));
+  }
+
+  // Feature
+  bool Enabled() const override {
+    return config_.enable_vehicle_hal_grpc_server() &&
+           FileExists(VehicleHalGrpcServerBinary());
+  }
+  std::string Name() const override { return "VehicleHalServer"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override { return true; }
+
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class ConsoleForwarder : public CommandSource, public DiagnosticInformation {
+ public:
+  INJECT(ConsoleForwarder(const CuttlefishConfig& config,
+                          const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+  // DiagnosticInformation
+  std::vector<std::string> Diagnostics() const override {
+    if (Enabled()) {
+      return {"To access the console run: screen " + instance_.console_path()};
+    } else {
+      return {"Serial console is disabled; use -console=true to enable it."};
     }
   }
-}
 
-void LaunchVehicleHalServerIfEnabled(const CuttlefishConfig& config,
-                                      ProcessMonitor* process_monitor) {
-  if (!config.enable_vehicle_hal_grpc_server() ||
-    !FileExists(config.vehicle_hal_grpc_server_binary())) {
-    return;
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command console_forwarder_cmd(ConsoleForwarderBinary());
+
+    console_forwarder_cmd.AddParameter("--console_in_fd=",
+                                       console_forwarder_in_wr_);
+    console_forwarder_cmd.AddParameter("--console_out_fd=",
+                                       console_forwarder_out_rd_);
+    return single_element_emplace(std::move(console_forwarder_cmd));
   }
 
-  Command grpc_server(config.vehicle_hal_grpc_server_binary());
-  auto instance = config.ForDefaultInstance();
+  // Feature
+  bool Enabled() const override { return config_.console(); }
+  std::string Name() const override { return "ConsoleForwarder"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
 
-  const unsigned vhal_server_cid = 2;
-  const unsigned vhal_server_port = instance.vehicle_hal_server_port();
-  const std::string vhal_server_power_state_file =
-      AbsolutePath(instance.PerInstancePath("power_state"));
-  const std::string vhal_server_power_state_socket =
-      AbsolutePath(instance.PerInstancePath("power_state_socket"));
+ protected:
+  bool Setup() override {
+    auto console_in_pipe_name = instance_.console_in_pipe_name();
+    if (mkfifo(console_in_pipe_name.c_str(), 0600) != 0) {
+      auto error = errno;
+      LOG(ERROR) << "Failed to create console input fifo for crosvm: "
+                 << strerror(error);
+      return false;
+    }
 
-  grpc_server.AddParameter("--server_cid=", vhal_server_cid);
-  grpc_server.AddParameter("--server_port=", vhal_server_port);
-  grpc_server.AddParameter("--power_state_file=", vhal_server_power_state_file);
-  grpc_server.AddParameter("--power_state_socket=", vhal_server_power_state_socket);
-  process_monitor->AddCommand(std::move(grpc_server));
-}
+    auto console_out_pipe_name = instance_.console_out_pipe_name();
+    if (mkfifo(console_out_pipe_name.c_str(), 0660) != 0) {
+      auto error = errno;
+      LOG(ERROR) << "Failed to create console output fifo for crosvm: "
+                 << strerror(error);
+      return false;
+    }
 
-void LaunchConsoleForwarderIfEnabled(const CuttlefishConfig& config,
-                                     ProcessMonitor* process_monitor)
-{
-  if (!config.console()) {
-    return;
+    // These fds will only be read from or written to, but open them with
+    // read and write access to keep them open in case the subprocesses exit
+    console_forwarder_in_wr_ =
+        SharedFD::Open(console_in_pipe_name.c_str(), O_RDWR);
+    if (!console_forwarder_in_wr_->IsOpen()) {
+      LOG(ERROR) << "Failed to open console_forwarder input fifo for writes: "
+                 << console_forwarder_in_wr_->StrError();
+      return false;
+    }
+
+    console_forwarder_out_rd_ =
+        SharedFD::Open(console_out_pipe_name.c_str(), O_RDWR);
+    if (!console_forwarder_out_rd_->IsOpen()) {
+      LOG(ERROR) << "Failed to open console_forwarder output fifo for reads: "
+                 << console_forwarder_out_rd_->StrError();
+      return false;
+    }
+    return true;
   }
 
-  Command console_forwarder_cmd(ConsoleForwarderBinary());
-  auto instance = config.ForDefaultInstance();
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD console_forwarder_in_wr_;
+  SharedFD console_forwarder_out_rd_;
+};
 
-  auto console_in_pipe_name = instance.console_in_pipe_name();
-  if (mkfifo(console_in_pipe_name.c_str(), 0600) != 0) {
-    auto error = errno;
-    LOG(ERROR) << "Failed to create console input fifo for crosvm: "
-               << strerror(error);
-    return;
-  }
-
-  auto console_out_pipe_name = instance.console_out_pipe_name();
-  if (mkfifo(console_out_pipe_name.c_str(), 0660) != 0) {
-    auto error = errno;
-    LOG(ERROR) << "Failed to create console output fifo for crosvm: "
-               << strerror(error);
-    return;
-  }
-
-  // These fds will only be read from or written to, but open them with
-  // read and write access to keep them open in case the subprocesses exit
-  SharedFD console_forwarder_in_wr =
-      SharedFD::Open(console_in_pipe_name.c_str(), O_RDWR);
-  if (!console_forwarder_in_wr->IsOpen()) {
-    LOG(ERROR) << "Failed to open console_forwarder input fifo for writes: "
-               << console_forwarder_in_wr->StrError();
-    return;
-  }
-
-  SharedFD console_forwarder_out_rd =
-      SharedFD::Open(console_out_pipe_name.c_str(), O_RDWR);
-  if (!console_forwarder_out_rd->IsOpen()) {
-    LOG(ERROR) << "Failed to open console_forwarder output fifo for reads: "
-               << console_forwarder_out_rd->StrError();
-    return;
-  }
-
-  console_forwarder_cmd.AddParameter("--console_in_fd=", console_forwarder_in_wr);
-  console_forwarder_cmd.AddParameter("--console_out_fd=", console_forwarder_out_rd);
-  process_monitor->AddCommand(std::move(console_forwarder_cmd));
+using PublicDeps = fruit::Required<const CuttlefishConfig,
+                                   const CuttlefishConfig::InstanceSpecific>;
+fruit::Component<PublicDeps, KernelLogPipeProvider> launchComponent() {
+  using InternalDeps = fruit::Required<const CuttlefishConfig,
+                                       const CuttlefishConfig::InstanceSpecific,
+                                       KernelLogPipeProvider>;
+  using Multi = Multibindings<InternalDeps>;
+  using Bases = Multi::Bases<CommandSource, DiagnosticInformation, Feature>;
+  return fruit::createComponent()
+      .bind<KernelLogPipeProvider, KernelLogMonitor>()
+      .install(Bases::Impls<BluetoothConnector>)
+      .install(Bases::Impls<ConfigServer>)
+      .install(Bases::Impls<ConsoleForwarder>)
+      .install(Bases::Impls<GnssGrpcProxyServer>)
+      .install(Bases::Impls<KernelLogMonitor>)
+      .install(Bases::Impls<LogcatReceiver>)
+      .install(Bases::Impls<MetricsService>)
+      .install(Bases::Impls<RootCanal>)
+      .install(Bases::Impls<SecureEnvironment>)
+      .install(Bases::Impls<TombstoneReceiver>)
+      .install(Bases::Impls<VehicleHalServer>);
 }
 
 } // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch.h b/host/commands/run_cvd/launch.h
index f265bf3..2cfcb33 100644
--- a/host/commands/run_cvd/launch.h
+++ b/host/commands/run_cvd/launch.h
@@ -1,63 +1,58 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 #pragma once
 
-#include <functional>
-#include <set>
+#include <fruit/fruit.h>
 #include <string>
+#include <vector>
 
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/subprocess.h"
-#include "host/commands/run_cvd/process_monitor.h"
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
 
 namespace cuttlefish {
 
-std::vector<SharedFD> LaunchKernelLogMonitor(
-    const CuttlefishConfig& config,
-    ProcessMonitor* process_monitor,
-    unsigned int number_of_event_pipes);
-void LaunchAdbConnectorIfEnabled(ProcessMonitor* process_monitor,
-                                 const CuttlefishConfig& config);
-void LaunchSocketVsockProxyIfEnabled(ProcessMonitor* process_monitor,
-                                     const CuttlefishConfig& config,
-                                     SharedFD adbd_events_pipe);
-void LaunchModemSimulatorIfEnabled(const CuttlefishConfig& config,
-                                   ProcessMonitor* process_monitor);
+class CommandSource : public virtual Feature {
+ public:
+  virtual ~CommandSource();
+  virtual std::vector<Command> Commands() = 0;
+};
 
-void LaunchVNCServer(const CuttlefishConfig& config,
-                     ProcessMonitor* process_monitor);
+class KernelLogPipeProvider : public virtual Feature {
+ public:
+  virtual ~KernelLogPipeProvider();
+  virtual SharedFD KernelLogPipe() = 0;
+};
 
-void LaunchTombstoneReceiver(const CuttlefishConfig& config,
-                             ProcessMonitor* process_monitor);
-void LaunchRootCanal(const CuttlefishConfig& config,
-                     ProcessMonitor* process_monitor);
-void LaunchLogcatReceiver(const CuttlefishConfig& config,
-                          ProcessMonitor* process_monitor);
-void LaunchConfigServer(const CuttlefishConfig& config,
-                        ProcessMonitor* process_monitor);
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>,
+                 KernelLogPipeProvider>
+launchComponent();
 
-void LaunchWebRTC(ProcessMonitor* process_monitor,
-                  const CuttlefishConfig& config,
-                  SharedFD kernel_log_events_pipe);
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>>
+launchModemComponent();
 
-void LaunchMetrics(ProcessMonitor* process_monitor);
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider,
+                                 const CuttlefishConfig::InstanceSpecific>>
+launchAdbComponent();
 
-void LaunchGnssGrpcProxyServerIfEnabled(const CuttlefishConfig& config,
-                                        ProcessMonitor* process_monitor);
-
-void LaunchSecureEnvironment(ProcessMonitor* process_monitor,
-                             const CuttlefishConfig& config);
-
-void LaunchBluetoothConnector(ProcessMonitor* process_monitor,
-                              const CuttlefishConfig& config);
-
-void LaunchCustomActionServers(Command& webrtc_cmd,
-                               ProcessMonitor* process_monitor,
-                               const CuttlefishConfig& config);
-
-void LaunchVehicleHalServerIfEnabled(const CuttlefishConfig& config,
-                                     ProcessMonitor* process_monitor);
-
-void LaunchConsoleForwarderIfEnabled(const CuttlefishConfig& config,
-                                     ProcessMonitor* process_monitor);
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider,
+                                 const CuttlefishConfig::InstanceSpecific>>
+launchStreamerComponent();
 
 } // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch_adb.cpp b/host/commands/run_cvd/launch_adb.cpp
new file mode 100644
index 0000000..7dbc55c
--- /dev/null
+++ b/host/commands/run_cvd/launch_adb.cpp
@@ -0,0 +1,219 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/commands/run_cvd/launch.h"
+
+#include <android-base/logging.h>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/libs/config/adb_config.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
+
+namespace cuttlefish {
+
+namespace {
+
+class AdbHelper {
+ public:
+  INJECT(AdbHelper(const CuttlefishConfig::InstanceSpecific& instance,
+                   const AdbConfig& config))
+      : instance_(instance), config_(config) {}
+
+  bool ModeEnabled(const AdbMode& mode) const {
+    return config_.adb_mode().count(mode) > 0;
+  }
+
+  std::string ConnectorTcpArg() const {
+    return "0.0.0.0:" + std::to_string(instance_.adb_host_port());
+  }
+
+  std::string ConnectorVsockArg() const {
+    return "vsock:" + std::to_string(instance_.vsock_guest_cid()) + ":5555";
+  }
+
+  bool VsockTunnelEnabled() const {
+    return instance_.vsock_guest_cid() > 2 && ModeEnabled(AdbMode::VsockTunnel);
+  }
+
+  bool VsockHalfTunnelEnabled() const {
+    return instance_.vsock_guest_cid() > 2 &&
+           ModeEnabled(AdbMode::VsockHalfTunnel);
+  }
+
+  bool TcpConnectorEnabled() const {
+    bool vsock_tunnel = VsockTunnelEnabled();
+    bool vsock_half_tunnel = VsockHalfTunnelEnabled();
+    return config_.run_adb_connector() && (vsock_tunnel || vsock_half_tunnel);
+  }
+
+  bool VsockConnectorEnabled() const {
+    return config_.run_adb_connector() && ModeEnabled(AdbMode::NativeVsock);
+  }
+
+ private:
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  const AdbConfig& config_;
+};
+
+class AdbConnector : public CommandSource {
+ public:
+  INJECT(AdbConnector(const AdbHelper& helper)) : helper_(helper) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command console_forwarder_cmd(ConsoleForwarderBinary());
+    Command adb_connector(AdbConnectorBinary());
+    std::set<std::string> addresses;
+
+    if (helper_.TcpConnectorEnabled()) {
+      addresses.insert(helper_.ConnectorTcpArg());
+    }
+    if (helper_.VsockConnectorEnabled()) {
+      addresses.insert(helper_.ConnectorVsockArg());
+    }
+
+    if (addresses.size() == 0) {
+      return {};
+    }
+    std::string address_arg = "--addresses=";
+    for (auto& arg : addresses) {
+      address_arg += arg + ",";
+    }
+    address_arg.pop_back();
+    adb_connector.AddParameter(address_arg);
+    std::vector<Command> commands;
+    commands.emplace_back(std::move(adb_connector));
+    return std::move(commands);
+  }
+
+  // Feature
+  bool Enabled() const override {
+    return helper_.TcpConnectorEnabled() || helper_.VsockConnectorEnabled();
+  }
+  std::string Name() const override { return "AdbConnector"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override { return true; }
+
+ private:
+  const AdbHelper& helper_;
+};
+
+class SocketVsockProxy : public CommandSource {
+ public:
+  INJECT(SocketVsockProxy(const AdbHelper& helper,
+                          const CuttlefishConfig::InstanceSpecific& instance,
+                          KernelLogPipeProvider& log_pipe_provider))
+      : helper_(helper),
+        instance_(instance),
+        log_pipe_provider_(log_pipe_provider) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    std::vector<Command> commands;
+    if (helper_.VsockTunnelEnabled()) {
+      Command adb_tunnel(SocketVsockProxyBinary());
+      adb_tunnel.AddParameter("-adbd_events_fd=", kernel_log_pipe_);
+      /**
+       * This socket_vsock_proxy (a.k.a. sv proxy) runs on the host. It assumes
+       * that another sv proxy runs inside the guest. see:
+       * shared/config/init.vendor.rc The sv proxy in the guest exposes
+       * vsock:cid:6520 across the cuttlefish instances in multi-tenancy. cid is
+       * different per instance.
+       *
+       * This host sv proxy should cooperate with the guest sv proxy. Thus, one
+       * end of the tunnel is vsock:cid:6520 regardless of instance number.
+       * Another end faces the host adb daemon via tcp. Thus, the server type is
+       * tcp here. The tcp port differs from instance to instance, and is
+       * instance.adb_host_port()
+       *
+       */
+      adb_tunnel.AddParameter("--server=tcp");
+      adb_tunnel.AddParameter("--vsock_port=6520");
+      adb_tunnel.AddParameter("--server_fd=", tcp_server_);
+      adb_tunnel.AddParameter("--vsock_cid=", instance_.vsock_guest_cid());
+      commands.emplace_back(std::move(adb_tunnel));
+    }
+    if (helper_.VsockHalfTunnelEnabled()) {
+      Command adb_tunnel(SocketVsockProxyBinary());
+      adb_tunnel.AddParameter("-adbd_events_fd=", kernel_log_pipe_);
+      /*
+       * This socket_vsock_proxy (a.k.a. sv proxy) runs on the host, and
+       * cooperates with the adbd inside the guest. See this file:
+       *  shared/device.mk, especially the line says "persist.adb.tcp.port="
+       *
+       * The guest adbd is listening on vsock:cid:5555 across cuttlefish
+       * instances. Sv proxy faces the host adb daemon via tcp. The server type
+       * should be therefore tcp, and the port should differ from instance to
+       * instance and be equal to instance.adb_host_port()
+       */
+      adb_tunnel.AddParameter("--server=tcp");
+      adb_tunnel.AddParameter("--vsock_port=", 5555);
+      adb_tunnel.AddParameter("--server_fd=", tcp_server_);
+      adb_tunnel.AddParameter("--vsock_cid=", instance_.vsock_guest_cid());
+      commands.emplace_back(std::move(adb_tunnel));
+    }
+    return commands;
+  }
+
+  // Feature
+  bool Enabled() const override {
+    return helper_.VsockTunnelEnabled() || helper_.VsockHalfTunnelEnabled();
+  }
+  std::string Name() const override { return "SocketVsockProxy"; }
+  std::unordered_set<Feature*> Dependencies() const override {
+    return {static_cast<Feature*>(&log_pipe_provider_)};
+  }
+
+ protected:
+  bool Setup() override {
+    tcp_server_ =
+        SharedFD::SocketLocalServer(instance_.adb_host_port(), SOCK_STREAM);
+    if (!tcp_server_->IsOpen()) {
+      LOG(ERROR) << "Unable to create socket_vsock_proxy server socket: "
+                 << tcp_server_->StrError();
+      return false;
+    }
+    kernel_log_pipe_ = log_pipe_provider_.KernelLogPipe();
+    return true;
+  }
+
+ private:
+  const AdbHelper& helper_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  KernelLogPipeProvider& log_pipe_provider_;
+  SharedFD kernel_log_pipe_;
+  SharedFD tcp_server_;
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider,
+                                 const CuttlefishConfig::InstanceSpecific>>
+launchAdbComponent() {
+  return fruit::createComponent()
+      .addMultibinding<CommandSource, AdbConnector>()
+      .addMultibinding<CommandSource, SocketVsockProxy>()
+      .addMultibinding<Feature, AdbConnector>()
+      .addMultibinding<Feature, SocketVsockProxy>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch_modem.cpp b/host/commands/run_cvd/launch_modem.cpp
new file mode 100644
index 0000000..0bf02bc
--- /dev/null
+++ b/host/commands/run_cvd/launch_modem.cpp
@@ -0,0 +1,146 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/commands/run_cvd/launch.h"
+
+#include <android-base/logging.h>
+#include <string.h>
+#include <sstream>
+#include <string>
+#include <unordered_set>
+#include <utility>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
+
+namespace cuttlefish {
+
+static bool StopModemSimulator(int id) {
+  std::string socket_name = "modem_simulator" + std::to_string(id);
+  auto monitor_sock =
+      SharedFD::SocketLocalClient(socket_name, true, SOCK_STREAM);
+  if (!monitor_sock->IsOpen()) {
+    LOG(ERROR) << "The connection to modem simulator is closed";
+    return false;
+  }
+  std::string msg("STOP");
+  if (monitor_sock->Write(msg.data(), msg.size()) < 0) {
+    monitor_sock->Close();
+    LOG(ERROR) << "Failed to send 'STOP' to modem simulator";
+    return false;
+  }
+  char buf[64] = {0};
+  if (monitor_sock->Read(buf, sizeof(buf)) <= 0) {
+    monitor_sock->Close();
+    LOG(ERROR) << "Failed to read message from modem simulator";
+    return false;
+  }
+  if (strcmp(buf, "OK")) {
+    monitor_sock->Close();
+    LOG(ERROR) << "Read '" << buf << "' instead of 'OK' from modem simulator";
+    return false;
+  }
+
+  return true;
+}
+
+class ModemSimulator : public CommandSource {
+ public:
+  INJECT(ModemSimulator(const CuttlefishConfig& config,
+                        const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command cmd(ModemSimulatorBinary(), [this](Subprocess* proc) {
+      auto stopped = StopModemSimulator(instance_.modem_simulator_host_id());
+      if (stopped) {
+        return StopperResult::kStopSuccess;
+      }
+      LOG(WARNING) << "Failed to stop modem simulator nicely, "
+                   << "attempting to KILL";
+      return KillSubprocess(proc) == StopperResult::kStopSuccess
+                 ? StopperResult::kStopCrash
+                 : StopperResult::kStopFailure;
+    });
+
+    auto sim_type = config_.modem_simulator_sim_type();
+    cmd.AddParameter(std::string{"-sim_type="} + std::to_string(sim_type));
+    cmd.AddParameter("-server_fds=");
+    bool first_socket = true;
+    for (const auto& socket : sockets_) {
+      if (!first_socket) {
+        cmd.AppendToLastParameter(",");
+      }
+      cmd.AppendToLastParameter(socket);
+      first_socket = false;
+    }
+
+    std::vector<Command> commands;
+    commands.emplace_back(std::move(cmd));
+    return commands;
+  }
+
+  // Feature
+  bool Enabled() const override {
+    if (!config_.enable_modem_simulator()) {
+      LOG(DEBUG) << "Modem simulator not enabled";
+    }
+    return config_.enable_modem_simulator();
+  }
+  std::string Name() const override { return "ModemSimulator"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override {
+    int instance_number = config_.modem_simulator_instance_number();
+    if (instance_number > 3 /* max value */ || instance_number < 0) {
+      LOG(ERROR)
+          << "Modem simulator instance number should range between 1 and 3";
+      return false;
+    }
+    auto ports = instance_.modem_simulator_ports();
+    for (int i = 0; i < instance_number; ++i) {
+      auto pos = ports.find(',');
+      auto temp = (pos != std::string::npos) ? ports.substr(0, pos) : ports;
+      auto port = std::stoi(temp);
+      ports = ports.substr(pos + 1);
+
+      auto socket = SharedFD::VsockServer(port, SOCK_STREAM);
+      CHECK(socket->IsOpen())
+          << "Unable to create modem simulator server socket: "
+          << socket->StrError();
+      sockets_.push_back(socket);
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  std::vector<SharedFD> sockets_;
+};
+
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>>
+launchModemComponent() {
+  return fruit::createComponent()
+      .addMultibinding<CommandSource, ModemSimulator>()
+      .addMultibinding<Feature, ModemSimulator>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch_streamer.cpp b/host/commands/run_cvd/launch_streamer.cpp
new file mode 100644
index 0000000..c7b83eb
--- /dev/null
+++ b/host/commands/run_cvd/launch_streamer.cpp
@@ -0,0 +1,365 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/commands/run_cvd/launch.h"
+
+#include <android-base/logging.h>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/files.h"
+#include "host/commands/run_cvd/reporting.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
+#include "host/libs/vm_manager/crosvm_manager.h"
+#include "host/libs/vm_manager/qemu_manager.h"
+
+namespace cuttlefish {
+
+namespace {
+
+SharedFD CreateUnixInputServer(const std::string& path) {
+  auto server =
+      SharedFD::SocketLocalServer(path.c_str(), false, SOCK_STREAM, 0666);
+  if (!server->IsOpen()) {
+    LOG(ERROR) << "Unable to create unix input server: " << server->StrError();
+    return {};
+  }
+  return server;
+}
+
+std::vector<Command> LaunchCustomActionServers(Command& webrtc_cmd,
+                                               const CuttlefishConfig& config) {
+  bool first = true;
+  std::vector<Command> commands;
+  for (const auto& custom_action : config.custom_actions()) {
+    if (custom_action.server) {
+      // Create a socket pair that will be used for communication between
+      // WebRTC and the action server.
+      SharedFD webrtc_socket, action_server_socket;
+      if (!SharedFD::SocketPair(AF_LOCAL, SOCK_STREAM, 0, &webrtc_socket,
+                                &action_server_socket)) {
+        LOG(ERROR) << "Unable to create custom action server socket pair: "
+                   << strerror(errno);
+        continue;
+      }
+
+      // Launch the action server, providing its socket pair fd as the only
+      // argument.
+      std::string binary = "bin/" + *(custom_action.server);
+      Command command(DefaultHostArtifactsPath(binary));
+      command.AddParameter(action_server_socket);
+      commands.emplace_back(std::move(command));
+
+      // Pass the WebRTC socket pair fd to WebRTC.
+      if (first) {
+        first = false;
+        webrtc_cmd.AddParameter("-action_servers=", *custom_action.server, ":",
+                                webrtc_socket);
+      } else {
+        webrtc_cmd.AppendToLastParameter(",", *custom_action.server, ":",
+                                         webrtc_socket);
+      }
+    }
+  }
+  return commands;
+}
+
+// Creates the frame and input sockets and add the relevant arguments to the vnc
+// server and webrtc commands
+class StreamerSockets : public virtual Feature {
+ public:
+  INJECT(StreamerSockets(const CuttlefishConfig& config,
+                         const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  void AppendCommandArguments(Command& cmd) {
+    if (config_.vm_manager() == vm_manager::QemuManager::name()) {
+      cmd.AddParameter("-write_virtio_input");
+    }
+    if (!touch_servers_.empty()) {
+      cmd.AddParameter("-touch_fds=", touch_servers_[0]);
+      for (int i = 1; i < touch_servers_.size(); ++i) {
+        cmd.AppendToLastParameter(",", touch_servers_[i]);
+      }
+    }
+    cmd.AddParameter("-keyboard_fd=", keyboard_server_);
+    cmd.AddParameter("-frame_server_fd=", frames_server_);
+    if (config_.enable_audio()) {
+      cmd.AddParameter("--audio_server_fd=", audio_server_);
+    }
+  }
+
+  // Feature
+  bool Enabled() const override {
+    bool is_qemu = config_.vm_manager() == vm_manager::QemuManager::name();
+    bool is_accelerated = config_.gpu_mode() != kGpuModeGuestSwiftshader;
+    return !(is_qemu && is_accelerated);
+  }
+  std::string Name() const override { return "StreamerSockets"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override {
+    auto use_vsockets = config_.vm_manager() == vm_manager::QemuManager::name();
+    for (int i = 0; i < config_.display_configs().size(); ++i) {
+      touch_servers_.push_back(
+          use_vsockets ? SharedFD::VsockServer(instance_.touch_server_port(),
+                                               SOCK_STREAM)
+                       : CreateUnixInputServer(instance_.touch_socket_path(i)));
+      if (!touch_servers_.back()->IsOpen()) {
+        LOG(ERROR) << "Could not open touch server: "
+                   << touch_servers_.back()->StrError();
+        return false;
+      }
+    }
+    if (use_vsockets) {
+      keyboard_server_ =
+          SharedFD::VsockServer(instance_.keyboard_server_port(), SOCK_STREAM);
+    } else {
+      keyboard_server_ =
+          CreateUnixInputServer(instance_.keyboard_socket_path());
+    }
+    if (!keyboard_server_->IsOpen()) {
+      LOG(ERROR) << "Failed to open keyboard server"
+                 << keyboard_server_->StrError();
+      return false;
+    }
+    frames_server_ = CreateUnixInputServer(instance_.frames_socket_path());
+    if (!frames_server_->IsOpen()) {
+      LOG(ERROR) << "Could not open frames server: "
+                 << frames_server_->StrError();
+      return false;
+    }
+    // TODO(schuffelen): Make this a separate optional feature?
+    if (config_.enable_audio()) {
+      auto path = config_.ForDefaultInstance().audio_server_path();
+      audio_server_ =
+          SharedFD::SocketLocalServer(path, false, SOCK_SEQPACKET, 0666);
+      if (!audio_server_->IsOpen()) {
+        LOG(ERROR) << "Could not create audio server: "
+                   << audio_server_->StrError();
+        return false;
+      }
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  std::vector<SharedFD> touch_servers_;
+  SharedFD keyboard_server_;
+  SharedFD frames_server_;
+  SharedFD audio_server_;
+};
+
+class VncServer : public virtual CommandSource, public DiagnosticInformation {
+ public:
+  INJECT(VncServer(const CuttlefishConfig& config,
+                   const CuttlefishConfig::InstanceSpecific& instance,
+                   StreamerSockets& sockets))
+      : config_(config), instance_(instance), sockets_(sockets) {}
+  // DiagnosticInformation
+  std::vector<std::string> Diagnostics() const override {
+    if (!Enabled()) {
+      return {};
+    }
+    std::ostringstream out;
+    out << "VNC server started on port "
+        << config_.ForDefaultInstance().vnc_server_port();
+    return {out.str()};
+  }
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command vnc_server(VncServerBinary());
+    vnc_server.AddParameter("-port=", instance_.vnc_server_port());
+    sockets_.AppendCommandArguments(vnc_server);
+
+    std::vector<Command> commands;
+    commands.emplace_back(std::move(vnc_server));
+    return commands;
+  }
+
+  // Feature
+  bool Enabled() const override {
+    return sockets_.Enabled() && config_.enable_vnc_server();
+  }
+  std::string Name() const override { return "VncServer"; }
+  std::unordered_set<Feature*> Dependencies() const override {
+    return {static_cast<Feature*>(&sockets_)};
+  }
+
+ protected:
+  bool Setup() override { return true; }
+
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  StreamerSockets& sockets_;
+};
+
+class WebRtcServer : public virtual CommandSource,
+                     public DiagnosticInformation {
+ public:
+  INJECT(WebRtcServer(const CuttlefishConfig& config,
+                      const CuttlefishConfig::InstanceSpecific& instance,
+                      StreamerSockets& sockets,
+                      KernelLogPipeProvider& log_pipe_provider))
+      : config_(config),
+        instance_(instance),
+        sockets_(sockets),
+        log_pipe_provider_(log_pipe_provider) {}
+  // DiagnosticInformation
+  std::vector<std::string> Diagnostics() const override {
+    if (!Enabled() || !config_.ForDefaultInstance().start_webrtc_sig_server()) {
+      // When WebRTC is enabled but an operator other than the one launched by
+      // run_cvd is used there is no way to know the url to which to point the
+      // browser to.
+      return {};
+    }
+    std::ostringstream out;
+    out << "Point your browser to https://" << config_.sig_server_address()
+        << ":" << config_.sig_server_port() << " to interact with the device.";
+    return {out.str()};
+  }
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    std::vector<Command> commands;
+    if (instance_.start_webrtc_sig_server()) {
+      Command sig_server(WebRtcSigServerBinary());
+      sig_server.AddParameter("-assets_dir=", config_.webrtc_assets_dir());
+      sig_server.AddParameter(
+          "-use_secure_http=",
+          config_.sig_server_secure() ? "true" : "false");
+      if (!config_.webrtc_certs_dir().empty()) {
+        sig_server.AddParameter("-certs_dir=", config_.webrtc_certs_dir());
+      }
+      sig_server.AddParameter("-http_server_port=", config_.sig_server_port());
+      commands.emplace_back(std::move(sig_server));
+    }
+
+    auto stopper = [host_socket = std::move(host_socket_)](Subprocess* proc) {
+      struct timeval timeout;
+      timeout.tv_sec = 3;
+      timeout.tv_usec = 0;
+      CHECK(host_socket->SetSockOpt(SOL_SOCKET, SO_RCVTIMEO, &timeout,
+                                    sizeof(timeout)) == 0)
+          << "Could not set receive timeout";
+
+      WriteAll(host_socket, "C");
+      char response[1];
+      int read_ret = host_socket->Read(response, sizeof(response));
+      if (read_ret != 0) {
+        LOG(ERROR) << "Failed to read response from webrtc";
+        return KillSubprocess(proc);
+      }
+      return KillSubprocess(proc) == StopperResult::kStopSuccess
+                 ? StopperResult::kStopCrash
+                 : StopperResult::kStopFailure;
+    };
+
+    Command webrtc(WebRtcBinary(), stopper);
+    webrtc.UnsetFromEnvironment({"http_proxy"});
+    sockets_.AppendCommandArguments(webrtc);
+    if (config_.vm_manager() == vm_manager::CrosvmManager::name()) {
+      webrtc.AddParameter("-switches_fd=", switches_server_);
+    }
+    // Currently there is no way to ensure the signaling server will already
+    // have bound the socket to the port by the time the webrtc process runs
+    // (the common technique of doing it from the launcher is not possible here
+    // as the server library being used creates its own sockets). However, this
+    // issue is mitigated slightly by doing some retrying and backoff in the
+    // webrtc process when connecting to the websocket, so it shouldn't be an
+    // issue most of the time.
+    webrtc.AddParameter("--command_fd=", client_socket_);
+    webrtc.AddParameter("-kernel_log_events_fd=", kernel_log_events_pipe_);
+
+    // TODO get from launcher params
+    for (auto& action : LaunchCustomActionServers(webrtc, config_)) {
+      commands.emplace_back(std::move(action));
+    }
+    commands.emplace_back(std::move(webrtc));
+
+    return commands;
+  }
+
+  // Feature
+  bool Enabled() const override {
+    return sockets_.Enabled() && config_.enable_webrtc();
+  }
+  std::string Name() const override { return "WebRtcServer"; }
+  std::unordered_set<Feature*> Dependencies() const override {
+    return {static_cast<Feature*>(&sockets_),
+            static_cast<Feature*>(&log_pipe_provider_)};
+  }
+
+ protected:
+  bool Setup() override {
+    if (!SharedFD::SocketPair(AF_LOCAL, SOCK_STREAM, 0, &client_socket_,
+                              &host_socket_)) {
+      LOG(ERROR) << "Could not open command socket for webRTC";
+      return false;
+    }
+    if (config_.vm_manager() == vm_manager::CrosvmManager::name()) {
+      switches_server_ =
+          CreateUnixInputServer(instance_.switches_socket_path());
+      if (!switches_server_->IsOpen()) {
+        LOG(ERROR) << "Could not open switches server: "
+                   << switches_server_->StrError();
+        return false;
+      }
+    }
+    kernel_log_events_pipe_ = log_pipe_provider_.KernelLogPipe();
+    if (!kernel_log_events_pipe_->IsOpen()) {
+      LOG(ERROR) << "Failed to get a kernel log events pipe: "
+                 << kernel_log_events_pipe_->StrError();
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  StreamerSockets& sockets_;
+  KernelLogPipeProvider& log_pipe_provider_;
+  SharedFD kernel_log_events_pipe_;
+  SharedFD client_socket_;
+  SharedFD host_socket_;
+  SharedFD switches_server_;
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider,
+                                 const CuttlefishConfig::InstanceSpecific>>
+launchStreamerComponent() {
+  return fruit::createComponent()
+      .addMultibinding<CommandSource, WebRtcServer>()
+      .addMultibinding<CommandSource, VncServer>()
+      .addMultibinding<DiagnosticInformation, WebRtcServer>()
+      .addMultibinding<DiagnosticInformation, VncServer>()
+      .addMultibinding<Feature, StreamerSockets>()
+      .addMultibinding<Feature, WebRtcServer>()
+      .addMultibinding<Feature, VncServer>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/run_cvd/main.cc b/host/commands/run_cvd/main.cc
index 13f840b..4c17b7d 100644
--- a/host/commands/run_cvd/main.cc
+++ b/host/commands/run_cvd/main.cc
@@ -14,375 +14,136 @@
  * limitations under the License.
  */
 
-#include <limits.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/prctl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <fcntl.h>
 #include <unistd.h>
-
-#include <algorithm>
-#include <functional>
-#include <iostream>
 #include <fstream>
-#include <iomanip>
 #include <memory>
-#include <sstream>
 #include <string>
-#include <thread>
+#include <utility>
 #include <vector>
 
 #include <android-base/logging.h>
 #include <android-base/strings.h>
+#include <fruit/fruit.h>
 #include <gflags/gflags.h>
 
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
-#include "common/libs/fs/shared_select.h"
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
-#include "common/libs/utils/network.h"
 #include "common/libs/utils/size_utils.h"
 #include "common/libs/utils/subprocess.h"
 #include "common/libs/utils/tee_logging.h"
 #include "host/commands/run_cvd/boot_state_machine.h"
 #include "host/commands/run_cvd/launch.h"
 #include "host/commands/run_cvd/process_monitor.h"
+#include "host/commands/run_cvd/reporting.h"
 #include "host/commands/run_cvd/runner_defs.h"
+#include "host/commands/run_cvd/server_loop.h"
+#include "host/commands/run_cvd/validate.h"
+#include "host/libs/config/adb_config.h"
+#include "host/libs/config/config_fragment.h"
 #include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/config/data_image.h"
-#include "host/libs/config/kernel_args.h"
-#include "host/libs/vm_manager/crosvm_manager.h"
-#include "host/libs/vm_manager/host_configuration.h"
-#include "host/libs/vm_manager/qemu_manager.h"
 #include "host/libs/vm_manager/vm_manager.h"
 
-DEFINE_int32(reboot_notification_fd, -1,
-             "A file descriptor to notify when boot completes.");
-
 namespace cuttlefish {
 
 using vm_manager::GetVmManager;
-using vm_manager::ValidateHostConfiguration;
 
 namespace {
 
-constexpr char kGreenColor[] = "\033[1;32m";
-constexpr char kResetColor[] = "\033[0m";
+class CuttlefishEnvironment : public Feature, public DiagnosticInformation {
+ public:
+  INJECT(
+      CuttlefishEnvironment(const CuttlefishConfig& config,
+                            const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
 
-bool WriteCuttlefishEnvironment(const CuttlefishConfig& config) {
-  auto env = SharedFD::Open(config.cuttlefish_env_path().c_str(),
-                            O_CREAT | O_RDWR, 0755);
-  if (!env->IsOpen()) {
-    LOG(ERROR) << "Unable to create cuttlefish.env file";
-    return false;
+  // DiagnosticInformation
+  std::vector<std::string> Diagnostics() const override {
+    auto config_path = instance_.PerInstancePath("cuttlefish_config.json");
+    return {
+        "Launcher log: " + instance_.launcher_log_path(),
+        "Instance configuration: " + config_path,
+        "Instance environment: " + config_.cuttlefish_env_path(),
+    };
   }
-  auto instance = config.ForDefaultInstance();
-  std::string config_env = "export CUTTLEFISH_PER_INSTANCE_PATH=\"" +
-                           instance.PerInstancePath(".") + "\"\n";
-  config_env += "export ANDROID_SERIAL=" + instance.adb_ip_and_port() + "\n";
-  env->Write(config_env.c_str(), config_env.size());
-  return true;
+
+  // Feature
+  bool Enabled() const override { return true; }
+  std::string Name() const override { return "CuttlefishEnvironment"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override {
+    auto env =
+        SharedFD::Open(config_.cuttlefish_env_path(), O_CREAT | O_RDWR, 0755);
+    if (!env->IsOpen()) {
+      LOG(ERROR) << "Unable to create cuttlefish.env file";
+      return false;
+    }
+    std::string config_env = "export CUTTLEFISH_PER_INSTANCE_PATH=\"" +
+                             instance_.PerInstancePath(".") + "\"\n";
+    config_env += "export ANDROID_SERIAL=" + instance_.adb_ip_and_port() + "\n";
+    auto written = WriteAll(env, config_env);
+    if (written != config_env.size()) {
+      LOG(ERROR) << "Failed to write all of \"" << config_env << "\", "
+                 << "only wrote " << written << " bytes. Error was "
+                 << env->StrError();
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+fruit::Component<ServerLoop> runCvdComponent(
+    const CuttlefishConfig* config,
+    const CuttlefishConfig::InstanceSpecific* instance) {
+  return fruit::createComponent()
+      .addMultibinding<DiagnosticInformation, CuttlefishEnvironment>()
+      .addMultibinding<Feature, CuttlefishEnvironment>()
+      .bindInstance(*config)
+      .bindInstance(*instance)
+      .install(AdbConfigComponent)
+      .install(bootStateMachineComponent)
+      .install(launchAdbComponent)
+      .install(launchComponent)
+      .install(launchModemComponent)
+      .install(launchStreamerComponent)
+      .install(serverLoopComponent)
+      .install(validationComponent);
 }
 
-// Forks and returns the write end of a pipe to the child process. The parent
-// process waits for boot events to come through the pipe and exits accordingly.
-SharedFD DaemonizeLauncher(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  SharedFD read_end, write_end;
-  if (!SharedFD::Pipe(&read_end, &write_end)) {
-    LOG(ERROR) << "Unable to create pipe";
-    return {}; // a closed FD
-  }
-  auto pid = fork();
-  if (pid) {
-    // Explicitly close here, otherwise we may end up reading forever if the
-    // child process dies.
-    write_end->Close();
-    RunnerExitCodes exit_code;
-    auto bytes_read = read_end->Read(&exit_code, sizeof(exit_code));
-    if (bytes_read != sizeof(exit_code)) {
-      LOG(ERROR) << "Failed to read a complete exit code, read " << bytes_read
-                 << " bytes only instead of the expected " << sizeof(exit_code);
-      exit_code = RunnerExitCodes::kPipeIOError;
-    } else if (exit_code == RunnerExitCodes::kSuccess) {
-      LOG(INFO) << "Virtual device booted successfully";
-    } else if (exit_code == RunnerExitCodes::kVirtualDeviceBootFailed) {
-      LOG(ERROR) << "Virtual device failed to boot";
-    } else {
-      LOG(ERROR) << "Unexpected exit code: " << exit_code;
-    }
-    if (exit_code == RunnerExitCodes::kSuccess) {
-      LOG(INFO) << kBootCompletedMessage;
-    } else {
-      LOG(INFO) << kBootFailedMessage;
-    }
-    std::exit(exit_code);
-  } else {
-    // The child returns the write end of the pipe
-    if (daemon(/*nochdir*/ 1, /*noclose*/ 1) != 0) {
-      LOG(ERROR) << "Failed to daemonize child process: " << strerror(errno);
-      std::exit(RunnerExitCodes::kDaemonizationError);
-    }
-    // Redirect standard I/O
-    auto log_path = instance.launcher_log_path();
-    auto log = SharedFD::Open(log_path.c_str(), O_CREAT | O_WRONLY | O_APPEND,
-                              S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
-    if (!log->IsOpen()) {
-      LOG(ERROR) << "Failed to create launcher log file: " << log->StrError();
-      std::exit(RunnerExitCodes::kDaemonizationError);
-    }
-    ::android::base::SetLogger(
-        TeeLogger({{LogFileSeverity(), log, MetadataLevel::FULL}}));
-    auto dev_null = SharedFD::Open("/dev/null", O_RDONLY);
-    if (!dev_null->IsOpen()) {
-      LOG(ERROR) << "Failed to open /dev/null: " << dev_null->StrError();
-      std::exit(RunnerExitCodes::kDaemonizationError);
-    }
-    if (dev_null->UNMANAGED_Dup2(0) < 0) {
-      LOG(ERROR) << "Failed dup2 stdin: " << dev_null->StrError();
-      std::exit(RunnerExitCodes::kDaemonizationError);
-    }
-    if (log->UNMANAGED_Dup2(1) < 0) {
-      LOG(ERROR) << "Failed dup2 stdout: " << log->StrError();
-      std::exit(RunnerExitCodes::kDaemonizationError);
-    }
-    if (log->UNMANAGED_Dup2(2) < 0) {
-      LOG(ERROR) << "Failed dup2 seterr: " << log->StrError();
-      std::exit(RunnerExitCodes::kDaemonizationError);
-    }
-
-    read_end->Close();
-    return write_end;
-  }
-}
-
-bool CreateQcowOverlay(const std::string& crosvm_path,
-                       const std::string& backing_file,
-                       const std::string& output_overlay_path) {
-  Command crosvm_qcow2_cmd(crosvm_path);
-  crosvm_qcow2_cmd.AddParameter("create_qcow2");
-  crosvm_qcow2_cmd.AddParameter("--backing_file=", backing_file);
-  crosvm_qcow2_cmd.AddParameter(output_overlay_path);
-  int success = crosvm_qcow2_cmd.Start().Wait();
-  if (success != 0) {
-    LOG(ERROR) << "Unable to run crosvm create_qcow2. Exited with status " << success;
-    return false;
-  }
-  return true;
-}
-
-void DeleteFifos(const CuttlefishConfig::InstanceSpecific& instance) {
-  // TODO(schuffelen): Create these FIFOs in assemble_cvd instead of run_cvd.
-  std::vector<std::string> pipes = {
-      instance.kernel_log_pipe_name(),
-      instance.console_in_pipe_name(),
-      instance.console_out_pipe_name(),
-      instance.logcat_pipe_name(),
-      instance.PerInstanceInternalPath("keymaster_fifo_vm.in"),
-      instance.PerInstanceInternalPath("keymaster_fifo_vm.out"),
-      instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"),
-      instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
-      instance.PerInstanceInternalPath("bt_fifo_vm.in"),
-      instance.PerInstanceInternalPath("bt_fifo_vm.out"),
-  };
-  for (const auto& pipe : pipes) {
-    unlink(pipe.c_str());
-  }
-}
-
-bool PowerwashFiles() {
-  auto config = CuttlefishConfig::Get();
-  if (!config) {
-    LOG(ERROR) << "Could not load the config.";
-    return false;
-  }
-  auto instance = config->ForDefaultInstance();
-
-  DeleteFifos(instance);
-
-  // TODO(schuffelen): Clean up duplication with assemble_cvd
-  auto kregistry_path = instance.access_kregistry_path();
-  unlink(kregistry_path.c_str());
-  CreateBlankImage(kregistry_path, 2 /* mb */, "none");
-
-  auto pstore_path = instance.pstore_path();
-  unlink(pstore_path.c_str());
-  CreateBlankImage(pstore_path, 2 /* mb */, "none");
-
-  auto sdcard_path = instance.sdcard_path();
-  auto sdcard_size = FileSize(sdcard_path);
-  unlink(sdcard_path.c_str());
-  // round up
-  auto sdcard_mb_size = (sdcard_size + (1 << 20) - 1) / (1 << 20);
-  LOG(DEBUG) << "Size in mb is " << sdcard_mb_size;
-  CreateBlankImage(sdcard_path, sdcard_mb_size, "sdcard");
-
-  auto overlay_path = instance.PerInstancePath("overlay.img");
-  unlink(overlay_path.c_str());
-  if (!CreateQcowOverlay(config->crosvm_binary(),
-                         instance.os_composite_disk_path(), overlay_path)) {
-    LOG(ERROR) << "CreateQcowOverlay failed";
-    return false;
-  }
-  return true;
-}
-
-void RestartRunCvd(const CuttlefishConfig& config, int notification_fd) {
-  auto config_path = config.AssemblyPath("cuttlefish_config.json");
-  auto followup_stdin = SharedFD::MemfdCreate("pseudo_stdin");
-  WriteAll(followup_stdin, config_path + "\n");
-  followup_stdin->LSeek(0, SEEK_SET);
-  followup_stdin->UNMANAGED_Dup2(0);
-
-  auto argv_vec = gflags::GetArgvs();
-  char** argv = new char*[argv_vec.size() + 2];
-  for (size_t i = 0; i < argv_vec.size(); i++) {
-    argv[i] = argv_vec[i].data();
-  }
-  // Will take precedence over any earlier arguments.
-  std::string reboot_notification =
-      "-reboot_notification_fd=" + std::to_string(notification_fd);
-  argv[argv_vec.size()] = reboot_notification.data();
-  argv[argv_vec.size() + 1] = nullptr;
-
-  execv("/proc/self/exe", argv);
-  // execve should not return, so something went wrong.
-  PLOG(ERROR) << "execv returned: ";
-}
-
-void ServerLoop(SharedFD server, ProcessMonitor* process_monitor) {
-  while (true) {
-    // TODO: use select to handle simultaneous connections.
-    auto client = SharedFD::Accept(*server);
-    LauncherAction action;
-    while (client->IsOpen() && client->Read(&action, sizeof(action)) > 0) {
-      switch (action) {
-        case LauncherAction::kStop:
-          if (process_monitor->StopMonitoredProcesses()) {
-            auto response = LauncherResponse::kSuccess;
-            client->Write(&response, sizeof(response));
-            std::exit(0);
-          } else {
-            auto response = LauncherResponse::kError;
-            client->Write(&response, sizeof(response));
-          }
-          break;
-        case LauncherAction::kStatus: {
-          // TODO(schuffelen): Return more information on a side channel
-          auto response = LauncherResponse::kSuccess;
-          client->Write(&response, sizeof(response));
-          break;
-        }
-        case LauncherAction::kPowerwash: {
-          LOG(INFO) << "Received a Powerwash request from the monitor socket";
-          if (!process_monitor->StopMonitoredProcesses()) {
-            LOG(ERROR) << "Stopping processes failed.";
-            auto response = LauncherResponse::kError;
-            client->Write(&response, sizeof(response));
-            break;
-          }
-          if (!PowerwashFiles()) {
-            LOG(ERROR) << "Powerwashing files failed.";
-            auto response = LauncherResponse::kError;
-            client->Write(&response, sizeof(response));
-            break;
-          }
-          auto response = LauncherResponse::kSuccess;
-          client->Write(&response, sizeof(response));
-
-          auto config = CuttlefishConfig::Get();
-          CHECK(config) << "Could not load config";
-          RestartRunCvd(*config, client->UNMANAGED_Dup());
-          // RestartRunCvd should not return, so something went wrong.
-          response = LauncherResponse::kError;
-          client->Write(&response, sizeof(response));
-          LOG(FATAL) << "run_cvd in a bad state";
-          break;
-        }
-        case LauncherAction::kRestart: {
-          if (!process_monitor->StopMonitoredProcesses()) {
-            LOG(ERROR) << "Stopping processes failed.";
-            auto response = LauncherResponse::kError;
-            client->Write(&response, sizeof(response));
-            break;
-          }
-
-          auto config = CuttlefishConfig::Get();
-          CHECK(config) << "Could not load config";
-          auto instance = config->ForDefaultInstance();
-          DeleteFifos(instance);
-
-          auto response = LauncherResponse::kSuccess;
-          client->Write(&response, sizeof(response));
-          CHECK(config) << "Could not load config";
-          RestartRunCvd(*config, client->UNMANAGED_Dup());
-          // RestartRunCvd should not return, so something went wrong.
-          response = LauncherResponse::kError;
-          client->Write(&response, sizeof(response));
-          LOG(FATAL) << "run_cvd in a bad state";
-          break;
-        }
-        default:
-          LOG(ERROR) << "Unrecognized launcher action: "
-                     << static_cast<char>(action);
-          auto response = LauncherResponse::kError;
-          client->Write(&response, sizeof(response));
-      }
-    }
-  }
-}
-
-std::string GetConfigFilePath(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  return instance.PerInstancePath("cuttlefish_config.json");
-}
-
-void PrintStreamingInformation(const CuttlefishConfig& config) {
-  if (config.ForDefaultInstance().start_webrtc_sig_server()) {
-    // TODO (jemoreira): Change this when webrtc is moved to the debian package.
-    LOG(INFO) << kGreenColor << "Point your browser to https://"
-              << config.sig_server_address() << ":" << config.sig_server_port()
-              << " to interact with the device." << kResetColor;
-  } else if (config.enable_vnc_server()) {
-    LOG(INFO) << kGreenColor << "VNC server started on port "
-              << config.ForDefaultInstance().vnc_server_port() << kResetColor;
-  }
-  // When WebRTC is enabled but an operator other than the one launched by
-  // run_cvd is used there is no way to know the url to which to point the
-  // browser to.
-}
-
-}  // namespace
-
-int RunCvdMain(int argc, char** argv) {
-  setenv("ANDROID_LOG_TAGS", "*:v", /* overwrite */ 0);
-  ::android::base::InitLogging(argv, android::base::StderrLogger);
-  google::ParseCommandLineFlags(&argc, &argv, false);
-
+bool IsStdinValid() {
   if (isatty(0)) {
-    LOG(FATAL) << "stdin was a tty, expected to be passed the output of a previous stage. "
+    LOG(ERROR) << "stdin was a tty, expected to be passed the output of a "
+                  "previous stage. "
                << "Did you mean to run launch_cvd?";
-    return RunnerExitCodes::kInvalidHostConfiguration;
+    return false;
   } else {
     int error_num = errno;
     if (error_num == EBADF) {
-      LOG(FATAL) << "stdin was not a valid file descriptor, expected to be passed the output "
+      LOG(ERROR) << "stdin was not a valid file descriptor, expected to be "
+                    "passed the output "
                  << "of assemble_cvd. Did you mean to run launch_cvd?";
-      return RunnerExitCodes::kInvalidHostConfiguration;
+      return false;
     }
   }
+  return true;
+}
 
+const CuttlefishConfig* FindConfigFromStdin() {
   std::string input_files_str;
   {
     auto input_fd = SharedFD::Dup(0);
     auto bytes_read = ReadAll(input_fd, &input_files_str);
     if (bytes_read < 0) {
-      LOG(FATAL) << "Failed to read input files. Error was \"" << input_fd->StrError() << "\"";
+      LOG(ERROR) << "Failed to read input files. Error was \""
+                 << input_fd->StrError() << "\"";
+      return nullptr;
     }
   }
   std::vector<std::string> input_files = android::base::Split(input_files_str, "\n");
@@ -393,186 +154,85 @@
       setenv(kCuttlefishConfigEnvVarName, file.c_str(), /* overwrite */ false);
     }
   }
-  if (!found_config) {
-    return RunnerExitCodes::kCuttlefishConfigurationInitError;
-  }
+  return CuttlefishConfig::Get();
+}
 
-  auto config = CuttlefishConfig::Get();
-  auto instance = config->ForDefaultInstance();
-
+void ConfigureLogs(const CuttlefishConfig& config,
+                   const CuttlefishConfig::InstanceSpecific& instance) {
   auto log_path = instance.launcher_log_path();
 
-  {
-    std::ofstream launcher_log_ofstream(log_path.c_str());
-    auto assembly_path = config->AssemblyPath("assemble_cvd.log");
-    std::ifstream assembly_log_ifstream(assembly_path);
-    if (assembly_log_ifstream) {
-      auto assemble_log = ReadFile(assembly_path);
-      launcher_log_ofstream << assemble_log;
-    }
+  std::ofstream launcher_log_ofstream(log_path.c_str());
+  auto assembly_path = config.AssemblyPath("assemble_cvd.log");
+  std::ifstream assembly_log_ifstream(assembly_path);
+  if (assembly_log_ifstream) {
+    auto assemble_log = ReadFile(assembly_path);
+    launcher_log_ofstream << assemble_log;
   }
   ::android::base::SetLogger(LogToStderrAndFiles({log_path}));
+}
 
+bool ChdirIntoRuntimeDir(const CuttlefishConfig::InstanceSpecific& instance) {
   // Change working directory to the instance directory as early as possible to
   // ensure all host processes have the same working dir. This helps stop_cvd
   // find the running processes when it can't establish a communication with the
   // launcher.
   auto chdir_ret = chdir(instance.instance_dir().c_str());
   if (chdir_ret != 0) {
-    auto error = errno;
-    LOG(ERROR) << "Unable to change dir into instance directory ("
-               << instance.instance_dir() << "): " << strerror(error);
-    return RunnerExitCodes::kInstanceDirCreationError;
+    PLOG(ERROR) << "Unable to change dir into instance directory ("
+                << instance.instance_dir() << "): ";
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+int RunCvdMain(int argc, char** argv) {
+  setenv("ANDROID_LOG_TAGS", "*:v", /* overwrite */ 0);
+  ::android::base::InitLogging(argv, android::base::StderrLogger);
+  google::ParseCommandLineFlags(&argc, &argv, false);
+
+  CHECK(IsStdinValid()) << "Invalid stdin";
+  auto config = FindConfigFromStdin();
+  CHECK(config) << "Could not find config";
+  auto instance = config->ForDefaultInstance();
+
+  ConfigureLogs(*config, instance);
+  CHECK(ChdirIntoRuntimeDir(instance)) << "Could not enter runtime dir";
+
+  fruit::Injector<ServerLoop> injector(runCvdComponent, config, &instance);
+
+  for (auto& fragment : injector.getMultibindings<ConfigFragment>()) {
+    CHECK(config->LoadFragment(*fragment)) << "Failed to load config fragment";
   }
 
-  auto used_tap_devices = TapInterfacesInUse();
-  if (used_tap_devices.count(instance.wifi_tap_name())) {
-    LOG(ERROR) << "Wifi TAP device already in use";
-    return RunnerExitCodes::kTapDeviceInUse;
-  } else if (used_tap_devices.count(instance.mobile_tap_name())) {
-    LOG(ERROR) << "Mobile TAP device already in use";
-    return RunnerExitCodes::kTapDeviceInUse;
-  } else if (config->ethernet() &&
-             used_tap_devices.count(instance.ethernet_tap_name())) {
-    LOG(ERROR) << "Ethernet TAP device already in use";
-  }
+  // One of the setup features can consume most output, so print this early.
+  DiagnosticInformation::PrintAll(
+      injector.getMultibindings<DiagnosticInformation>());
 
-  auto vm_manager = GetVmManager(config->vm_manager(), config->target_arch());
-
-#ifndef __ANDROID__
-  // Check host configuration
-  std::vector<std::string> config_commands;
-  if (!ValidateHostConfiguration(&config_commands)) {
-    LOG(ERROR) << "Validation of user configuration failed";
-    std::cout << "Execute the following to correctly configure:" << std::endl;
-    for (auto& command : config_commands) {
-      std::cout << "  " << command << std::endl;
-    }
-    std::cout << "You may need to logout for the changes to take effect"
-              << std::endl;
-    return RunnerExitCodes::kInvalidHostConfiguration;
-  }
-#endif
-
-  if (!WriteCuttlefishEnvironment(*config)) {
-    LOG(ERROR) << "Unable to write cuttlefish environment file";
-  }
-
-  PrintStreamingInformation(*config);
-
-  if (config->console()) {
-    LOG(INFO) << kGreenColor << "To access the console run: screen "
-              << instance.console_path() << kResetColor;
-  } else {
-    LOG(INFO) << kGreenColor
-              << "Serial console is disabled; use -console=true to enable it"
-              << kResetColor;
-  }
-
-  LOG(INFO) << kGreenColor
-            << "The following files contain useful debugging information:"
-            << kResetColor;
-  LOG(INFO) << kGreenColor
-            << "  Launcher log: " << instance.launcher_log_path()
-            << kResetColor;
-  LOG(INFO) << kGreenColor
-            << "  Android's logcat output: " << instance.logcat_path()
-            << kResetColor;
-  LOG(INFO) << kGreenColor
-            << "  Kernel log: " << instance.PerInstancePath("kernel.log")
-            << kResetColor;
-  LOG(INFO) << kGreenColor
-            << "  Instance configuration: " << GetConfigFilePath(*config)
-            << kResetColor;
-  LOG(INFO) << kGreenColor
-            << "  Instance environment: " << config->cuttlefish_env_path()
-            << kResetColor;
-
-  auto launcher_monitor_path = instance.launcher_monitor_socket_path();
-  auto launcher_monitor_socket = SharedFD::SocketLocalServer(
-      launcher_monitor_path.c_str(), false, SOCK_STREAM, 0666);
-  if (!launcher_monitor_socket->IsOpen()) {
-    LOG(ERROR) << "Error when opening launcher server: "
-               << launcher_monitor_socket->StrError();
-    return RunnerExitCodes::kMonitorCreationFailed;
-  }
-  SharedFD foreground_launcher_pipe;
-  if (config->run_as_daemon()) {
-    foreground_launcher_pipe = DaemonizeLauncher(*config);
-    if (!foreground_launcher_pipe->IsOpen()) {
-      return RunnerExitCodes::kDaemonizationError;
-    }
-  } else {
-    // Make sure the launcher runs in its own process group even when running in
-    // foreground
-    if (getsid(0) != getpid()) {
-      int retval = setpgid(0, 0);
-      if (retval) {
-        LOG(ERROR) << "Failed to create new process group: " << strerror(errno);
-        std::exit(RunnerExitCodes::kProcessGroupError);
-      }
-    }
-  }
-
-  SharedFD reboot_notification;
-  if (FLAGS_reboot_notification_fd >= 0) {
-    reboot_notification = SharedFD::Dup(FLAGS_reboot_notification_fd);
-    close(FLAGS_reboot_notification_fd);
-  }
+  const auto& features = injector.getMultibindings<Feature>();
+  CHECK(Feature::RunSetup(features)) << "Failed to run feature setup.";
 
   // Monitor and restart host processes supporting the CVD
   ProcessMonitor process_monitor(config->restart_subprocesses());
 
-  if (config->enable_metrics() == CuttlefishConfig::kYes) {
-    LaunchMetrics(&process_monitor);
+  for (auto& command_source : injector.getMultibindings<CommandSource>()) {
+    if (command_source->Enabled()) {
+      process_monitor.AddCommands(command_source->Commands());
+    }
   }
-  LaunchModemSimulatorIfEnabled(*config, &process_monitor);
-
-  auto event_pipes =
-      LaunchKernelLogMonitor(*config, &process_monitor, 3);
-  SharedFD boot_events_pipe = event_pipes[0];
-  SharedFD adbd_events_pipe = event_pipes[1];
-  SharedFD webrtc_events_pipe = event_pipes[2];
-  event_pipes.clear();
-
-  CvdBootStateMachine boot_state_machine(foreground_launcher_pipe,
-                                         reboot_notification, boot_events_pipe);
-
-  LaunchRootCanal(*config, &process_monitor);
-  LaunchLogcatReceiver(*config, &process_monitor);
-  LaunchConfigServer(*config, &process_monitor);
-  LaunchTombstoneReceiver(*config, &process_monitor);
-  LaunchGnssGrpcProxyServerIfEnabled(*config, &process_monitor);
-  LaunchSecureEnvironment(&process_monitor, *config);
-  if (config->enable_host_bluetooth()) {
-    LaunchBluetoothConnector(&process_monitor, *config);
-  }
-  LaunchVehicleHalServerIfEnabled(*config, &process_monitor);
-  LaunchConsoleForwarderIfEnabled(*config, &process_monitor);
 
   // The streamer needs to launch before the VMM because it serves on several
   // sockets (input devices, vsock frame server) when using crosvm.
-  if (config->enable_vnc_server()) {
-    LaunchVNCServer(*config, &process_monitor);
-  }
-  if (config->enable_webrtc()) {
-    LaunchWebRTC(&process_monitor, *config, webrtc_events_pipe);
-  }
 
   // Start the guest VM
-  auto vmm_commands = vm_manager->StartCommands(*config);
-  for (auto& vmm_cmd: vmm_commands) {
-    process_monitor.AddCommand(std::move(vmm_cmd));
-  }
-
-  // Start other host processes
-  LaunchSocketVsockProxyIfEnabled(&process_monitor, *config, adbd_events_pipe);
-  LaunchAdbConnectorIfEnabled(&process_monitor, *config);
+  auto vm_manager = GetVmManager(config->vm_manager(), config->target_arch());
+  process_monitor.AddCommands(vm_manager->StartCommands(*config));
 
   CHECK(process_monitor.StartAndMonitorProcesses())
       << "Could not start subprocesses";
 
-  ServerLoop(launcher_monitor_socket, &process_monitor); // Should not return
+  injector.get<ServerLoop&>().Run(process_monitor);  // Should not return
   LOG(ERROR) << "The server loop returned, it should never happen!!";
 
   return RunnerExitCodes::kServerError;
diff --git a/host/commands/run_cvd/process_monitor.cc b/host/commands/run_cvd/process_monitor.cc
index a4f7f72..53fc8b6 100644
--- a/host/commands/run_cvd/process_monitor.cc
+++ b/host/commands/run_cvd/process_monitor.cc
@@ -118,7 +118,7 @@
 }
 
 static void LogSubprocessExit(const std::string& name, pid_t pid, int wstatus) {
-  LOG(INFO) << "Detected exit of monitored subprocess " << name;
+  LOG(INFO) << "Detected unexpected exit of monitored subprocess " << name;
   if (WIFEXITED(wstatus)) {
     LOG(INFO) << "Subprocess " << name << " (" << pid
               << ") has exited with exit code " << WEXITSTATUS(wstatus);
@@ -195,21 +195,25 @@
   }
 
   parent_comms_thread.join(); // Should have exited if `running` is false
-  // Processes were started in the order they appear in the vector, stop them in
-  // reverse order for symmetry.
   auto stop = [](const auto& it) {
-    if (!it.proc->Stop()) {
+    auto stop_result = it.proc->Stop();
+    if (stop_result == StopperResult::kStopFailure) {
       LOG(WARNING) << "Error in stopping \"" << it.cmd->GetShortName() << "\"";
       return false;
     }
     int wstatus = 0;
-    auto ret = it.proc->Wait(&wstatus, 0);
-    if (ret < 0) {
+    auto pid = it.proc->Wait(&wstatus, 0);
+    if (pid < 0) {
       LOG(WARNING) << "Failed to wait for process " << it.cmd->GetShortName();
       return false;
     }
+    if (stop_result == StopperResult::kStopCrash) {
+      LogSubprocessExit(it.cmd->GetShortName(), pid, wstatus);
+    }
     return true;
   };
+  // Processes were started in the order they appear in the vector, stop them in
+  // reverse order for symmetry.
   size_t stopped = std::count_if(monitored.rbegin(), monitored.rend(), stop);
   LOG(DEBUG) << "Done monitoring subprocesses";
   return stopped == monitored.size();
diff --git a/host/commands/run_cvd/process_monitor.h b/host/commands/run_cvd/process_monitor.h
index 5d00096..18b8ab8 100644
--- a/host/commands/run_cvd/process_monitor.h
+++ b/host/commands/run_cvd/process_monitor.h
@@ -42,6 +42,12 @@
   // called before StartAndMonitorProcesses is called. OnSocketReadyCb will be
   // called inside a forked process.
   void AddCommand(Command cmd);
+  template <typename T>
+  void AddCommands(T&& commands) {
+    for (auto& command : commands) {
+      AddCommand(std::move(command));
+    }
+  }
 
   // Start all processes given by AddCommand.
   bool StartAndMonitorProcesses();
diff --git a/host/commands/run_cvd/reporting.cpp b/host/commands/run_cvd/reporting.cpp
new file mode 100644
index 0000000..db25185
--- /dev/null
+++ b/host/commands/run_cvd/reporting.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#include "host/commands/run_cvd/reporting.h"
+
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+#include <string>
+#include <vector>
+
+namespace cuttlefish {
+
+static constexpr char kGreenColor[] = "\033[1;32m";
+static constexpr char kResetColor[] = "\033[0m";
+
+DiagnosticInformation::~DiagnosticInformation() = default;
+
+void DiagnosticInformation::PrintAll(
+    const std::vector<DiagnosticInformation*>& infos) {
+  LOG(INFO) << kGreenColor
+            << "The following files contain useful debugging information:"
+            << kResetColor;
+  for (const auto& info : infos) {
+    for (const auto& line : info->Diagnostics()) {
+      LOG(INFO) << kGreenColor << "  " << line << kResetColor;
+    }
+  }
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/run_cvd/reporting.h
similarity index 64%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/run_cvd/reporting.h
index 9f25445..f6b4d5a 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/run_cvd/reporting.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,15 +14,20 @@
  * limitations under the License.
  */
 
-#include "common/libs/utils/size_utils.h"
+#pragma once
 
-#include <unistd.h>
+#include <fruit/fruit.h>
+#include <string>
+#include <vector>
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+class DiagnosticInformation {
+ public:
+  virtual ~DiagnosticInformation();
+  virtual std::vector<std::string> Diagnostics() const = 0;
+
+  static void PrintAll(const std::vector<DiagnosticInformation*>&);
+};
 
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/runner_defs.h b/host/commands/run_cvd/runner_defs.h
index e27597a..b51e894 100644
--- a/host/commands/run_cvd/runner_defs.h
+++ b/host/commands/run_cvd/runner_defs.h
@@ -44,6 +44,7 @@
   kTapDeviceInUse = 23,
   kTpmPassthroughError = 24,
   kModemSimulatorServerError = 25,
+  kSocketProxyServerError = 26,
 };
 
 // Actions supported by the launcher server
diff --git a/host/commands/run_cvd/server_loop.cpp b/host/commands/run_cvd/server_loop.cpp
new file mode 100644
index 0000000..2b69aef
--- /dev/null
+++ b/host/commands/run_cvd/server_loop.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/run_cvd/server_loop.h"
+
+#include <fruit/fruit.h>
+#include <gflags/gflags.h>
+#include <unistd.h>
+#include <string>
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/run_cvd/runner_defs.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/data_image.h"
+#include "host/libs/config/feature.h"
+
+namespace cuttlefish {
+
+namespace {
+
+bool CreateQcowOverlay(const std::string& crosvm_path,
+                       const std::string& backing_file,
+                       const std::string& output_overlay_path) {
+  Command crosvm_qcow2_cmd(crosvm_path);
+  crosvm_qcow2_cmd.AddParameter("create_qcow2");
+  crosvm_qcow2_cmd.AddParameter("--backing_file=", backing_file);
+  crosvm_qcow2_cmd.AddParameter(output_overlay_path);
+  int success = crosvm_qcow2_cmd.Start().Wait();
+  if (success != 0) {
+    LOG(ERROR) << "Unable to run crosvm create_qcow2. Exited with status "
+               << success;
+    return false;
+  }
+  return true;
+}
+
+class ServerLoopImpl : public ServerLoop, public Feature {
+ public:
+  INJECT(ServerLoopImpl(const CuttlefishConfig& config,
+                        const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // ServerLoop
+  void Run(ProcessMonitor& process_monitor) override {
+    while (true) {
+      // TODO: use select to handle simultaneous connections.
+      auto client = SharedFD::Accept(*server_);
+      LauncherAction action;
+      while (client->IsOpen() && client->Read(&action, sizeof(action)) > 0) {
+        switch (action) {
+          case LauncherAction::kStop:
+            if (process_monitor.StopMonitoredProcesses()) {
+              auto response = LauncherResponse::kSuccess;
+              client->Write(&response, sizeof(response));
+              std::exit(0);
+            } else {
+              auto response = LauncherResponse::kError;
+              client->Write(&response, sizeof(response));
+            }
+            break;
+          case LauncherAction::kStatus: {
+            // TODO(schuffelen): Return more information on a side channel
+            auto response = LauncherResponse::kSuccess;
+            client->Write(&response, sizeof(response));
+            break;
+          }
+          case LauncherAction::kPowerwash: {
+            LOG(INFO) << "Received a Powerwash request from the monitor socket";
+            if (!process_monitor.StopMonitoredProcesses()) {
+              LOG(ERROR) << "Stopping processes failed.";
+              auto response = LauncherResponse::kError;
+              client->Write(&response, sizeof(response));
+              break;
+            }
+            if (!PowerwashFiles()) {
+              LOG(ERROR) << "Powerwashing files failed.";
+              auto response = LauncherResponse::kError;
+              client->Write(&response, sizeof(response));
+              break;
+            }
+            auto response = LauncherResponse::kSuccess;
+            client->Write(&response, sizeof(response));
+
+            RestartRunCvd(client->UNMANAGED_Dup());
+            // RestartRunCvd should not return, so something went wrong.
+            response = LauncherResponse::kError;
+            client->Write(&response, sizeof(response));
+            LOG(FATAL) << "run_cvd in a bad state";
+            break;
+          }
+          case LauncherAction::kRestart: {
+            if (!process_monitor.StopMonitoredProcesses()) {
+              LOG(ERROR) << "Stopping processes failed.";
+              auto response = LauncherResponse::kError;
+              client->Write(&response, sizeof(response));
+              break;
+            }
+            DeleteFifos();
+
+            auto response = LauncherResponse::kSuccess;
+            client->Write(&response, sizeof(response));
+            RestartRunCvd(client->UNMANAGED_Dup());
+            // RestartRunCvd should not return, so something went wrong.
+            response = LauncherResponse::kError;
+            client->Write(&response, sizeof(response));
+            LOG(FATAL) << "run_cvd in a bad state";
+            break;
+          }
+          default:
+            LOG(ERROR) << "Unrecognized launcher action: "
+                       << static_cast<char>(action);
+            auto response = LauncherResponse::kError;
+            client->Write(&response, sizeof(response));
+        }
+      }
+    }
+  }
+
+  // Feature
+  bool Enabled() const override { return true; }
+  std::string Name() const override { return "ServerLoop"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() {
+    auto launcher_monitor_path = instance_.launcher_monitor_socket_path();
+    server_ = SharedFD::SocketLocalServer(launcher_monitor_path.c_str(), false,
+                                          SOCK_STREAM, 0666);
+    if (!server_->IsOpen()) {
+      LOG(ERROR) << "Error when opening launcher server: "
+                 << server_->StrError();
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  void DeleteFifos() {
+    // TODO(schuffelen): Create these FIFOs in assemble_cvd instead of run_cvd.
+    std::vector<std::string> pipes = {
+        instance_.kernel_log_pipe_name(),
+        instance_.console_in_pipe_name(),
+        instance_.console_out_pipe_name(),
+        instance_.logcat_pipe_name(),
+        instance_.PerInstanceInternalPath("keymaster_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("keymaster_fifo_vm.out"),
+        instance_.PerInstanceInternalPath("gatekeeper_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
+        instance_.PerInstanceInternalPath("bt_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("bt_fifo_vm.out"),
+    };
+    for (const auto& pipe : pipes) {
+      unlink(pipe.c_str());
+    }
+  }
+
+  bool PowerwashFiles() {
+    DeleteFifos();
+
+    // TODO(schuffelen): Clean up duplication with assemble_cvd
+    auto kregistry_path = instance_.access_kregistry_path();
+    unlink(kregistry_path.c_str());
+    CreateBlankImage(kregistry_path, 2 /* mb */, "none");
+
+    auto pstore_path = instance_.pstore_path();
+    unlink(pstore_path.c_str());
+    CreateBlankImage(pstore_path, 2 /* mb */, "none");
+
+    auto sdcard_path = instance_.sdcard_path();
+    auto sdcard_size = FileSize(sdcard_path);
+    unlink(sdcard_path.c_str());
+    // round up
+    auto sdcard_mb_size = (sdcard_size + (1 << 20) - 1) / (1 << 20);
+    LOG(DEBUG) << "Size in mb is " << sdcard_mb_size;
+    CreateBlankImage(sdcard_path, sdcard_mb_size, "sdcard");
+
+    auto overlay_path = instance_.PerInstancePath("overlay.img");
+    unlink(overlay_path.c_str());
+    if (!CreateQcowOverlay(config_.crosvm_binary(),
+                           config_.os_composite_disk_path(), overlay_path)) {
+      LOG(ERROR) << "CreateQcowOverlay failed";
+      return false;
+    }
+    return true;
+  }
+
+  void RestartRunCvd(int notification_fd) {
+    auto config_path = config_.AssemblyPath("cuttlefish_config.json");
+    auto followup_stdin = SharedFD::MemfdCreate("pseudo_stdin");
+    WriteAll(followup_stdin, config_path + "\n");
+    followup_stdin->LSeek(0, SEEK_SET);
+    followup_stdin->UNMANAGED_Dup2(0);
+
+    auto argv_vec = gflags::GetArgvs();
+    char** argv = new char*[argv_vec.size() + 2];
+    for (size_t i = 0; i < argv_vec.size(); i++) {
+      argv[i] = argv_vec[i].data();
+    }
+    // Will take precedence over any earlier arguments.
+    std::string reboot_notification =
+        "-reboot_notification_fd=" + std::to_string(notification_fd);
+    argv[argv_vec.size()] = reboot_notification.data();
+    argv[argv_vec.size() + 1] = nullptr;
+
+    execv("/proc/self/exe", argv);
+    // execve should not return, so something went wrong.
+    PLOG(ERROR) << "execv returned: ";
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD server_;
+};
+
+}  // namespace
+
+ServerLoop::~ServerLoop() = default;
+
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>,
+                 ServerLoop>
+serverLoopComponent() {
+  return fruit::createComponent()
+      .bind<ServerLoop, ServerLoopImpl>()
+      .addMultibinding<Feature, ServerLoopImpl>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/run_cvd/server_loop.h b/host/commands/run_cvd/server_loop.h
new file mode 100644
index 0000000..2364cb9
--- /dev/null
+++ b/host/commands/run_cvd/server_loop.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <fruit/fruit.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/commands/run_cvd/process_monitor.h"
+#include "host/libs/config/cuttlefish_config.h"
+
+namespace cuttlefish {
+
+class ServerLoop {
+ public:
+  virtual ~ServerLoop();
+  virtual void Run(ProcessMonitor& process_monitor) = 0;
+};
+
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>,
+                 ServerLoop>
+serverLoopComponent();
+}
diff --git a/host/commands/run_cvd/validate.cpp b/host/commands/run_cvd/validate.cpp
new file mode 100644
index 0000000..567c4e4
--- /dev/null
+++ b/host/commands/run_cvd/validate.cpp
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+#include <iostream>
+
+#include "common/libs/utils/network.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
+#include "host/libs/vm_manager/host_configuration.h"
+
+namespace cuttlefish {
+namespace {
+
+using vm_manager::ValidateHostConfiguration;
+
+class ValidateTapDevices : public Feature {
+ public:
+  INJECT(ValidateTapDevices(const CuttlefishConfig& config,
+                            const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  bool Enabled() const override { return true; }
+  std::string Name() const override { return "ValidateTapDevices"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override {
+    auto used_tap_devices = TapInterfacesInUse();
+    if (used_tap_devices.count(instance_.wifi_tap_name())) {
+      LOG(ERROR) << "Wifi TAP device already in use";
+      return false;
+    } else if (used_tap_devices.count(instance_.mobile_tap_name())) {
+      LOG(ERROR) << "Mobile TAP device already in use";
+      return false;
+    } else if (config_.ethernet() &&
+               used_tap_devices.count(instance_.ethernet_tap_name())) {
+      LOG(ERROR) << "Ethernet TAP device already in use";
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class ValidateHostConfigurationFeature : public Feature {
+ public:
+  INJECT(ValidateHostConfigurationFeature()) {}
+
+  bool Enabled() const override {
+#ifndef __ANDROID__
+    return true;
+#else
+    return false;
+#endif
+  }
+  std::string Name() const override { return "ValidateHostConfiguration"; }
+  std::unordered_set<Feature*> Dependencies() const override { return {}; }
+
+ protected:
+  bool Setup() override {
+    // Check host configuration
+    std::vector<std::string> config_commands;
+    if (!ValidateHostConfiguration(&config_commands)) {
+      LOG(ERROR) << "Validation of user configuration failed";
+      std::cout << "Execute the following to correctly configure:" << std::endl;
+      for (auto& command : config_commands) {
+        std::cout << "  " << command << std::endl;
+      }
+      std::cout << "You may need to logout for the changes to take effect"
+                << std::endl;
+      return false;
+    }
+    return true;
+  }
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>>
+validationComponent() {
+  return fruit::createComponent()
+      .addMultibinding<Feature, ValidateHostConfigurationFeature>()
+      .addMultibinding<Feature, ValidateTapDevices>();
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/run_cvd/validate.h
similarity index 63%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/run_cvd/validate.h
index 9f25445..99c5c07 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/run_cvd/validate.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-#include "common/libs/utils/size_utils.h"
+#pragma once
 
-#include <unistd.h>
+#include <fruit/fruit.h>
+
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>>
+validationComponent();
 
-}  // namespace cuttlefish
+}
diff --git a/host/commands/secure_env/Android.bp b/host/commands/secure_env/Android.bp
index 3ceeda5..bcc10ec 100644
--- a/host/commands/secure_env/Android.bp
+++ b/host/commands/secure_env/Android.bp
@@ -42,12 +42,16 @@
         "tpm_keymaster_context.cpp",
         "tpm_keymaster_enforcement.cpp",
         "tpm_random_source.cpp",
+        "tpm_remote_provisioning_context.cpp",
         "tpm_resource_manager.cpp",
         "tpm_serialize.cpp",
     ],
     shared_libs: [
         "libbase",
+        "libcppbor_external",
+        "libcppcose_rkp",
         "libcuttlefish_fs",
+        "libcuttlefish_kernel_log_monitor_utils",
         "libcuttlefish_security",
         "libcuttlefish_utils",
         "libgatekeeper",
diff --git a/host/commands/secure_env/hmac_serializable.cpp b/host/commands/secure_env/hmac_serializable.cpp
index c40b736..db055b0 100644
--- a/host/commands/secure_env/hmac_serializable.cpp
+++ b/host/commands/secure_env/hmac_serializable.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2020 The Android Open Source Project
+// 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.
@@ -16,6 +16,8 @@
 #include "hmac_serializable.h"
 
 #include <android-base/logging.h>
+#include <optional>
+#include <vector>
 
 #include "host/commands/secure_env/tpm_auth.h"
 #include "host/commands/secure_env/tpm_hmac.h"
@@ -23,13 +25,12 @@
 HmacSerializable::HmacSerializable(
     TpmResourceManager& resource_manager,
     std::function<TpmObjectSlot(TpmResourceManager&)> signing_key_fn,
-    uint32_t digest_size,
-    Serializable* wrapped) :
-    resource_manager_(resource_manager),
-    signing_key_fn_(signing_key_fn),
-    digest_size_(digest_size),
-    wrapped_(wrapped) {
-}
+    uint32_t digest_size, Serializable* wrapped, const Serializable* aad)
+    : resource_manager_(resource_manager),
+      signing_key_fn_(signing_key_fn),
+      digest_size_(digest_size),
+      wrapped_(wrapped),
+      aad_(aad) {}
 
 size_t HmacSerializable::SerializedSize() const {
   auto digest_size = sizeof(uint32_t) + digest_size_;
@@ -51,13 +52,13 @@
     LOG(ERROR) << "Could not retrieve key";
     return buf;
   }
+  auto maced_data = AppendAad(signed_data, wrapped_size);
+  if (!maced_data) {
+    return buf;
+  }
   auto hmac_data =
-    TpmHmac(
-        resource_manager_,
-        key->get(),
-        TpmAuth(ESYS_TR_PASSWORD),
-        signed_data,
-        wrapped_size);
+      TpmHmac(resource_manager_, key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              maced_data->data(), maced_data->size());
   if (!hmac_data) {
     LOG(ERROR) << "Failed to produce hmac";
     return buf;
@@ -99,13 +100,13 @@
     LOG(ERROR) << "Could not retrieve key";
     return false;
   }
+  auto maced_data = AppendAad(signed_data.get(), signed_data_size);
+  if (!maced_data) {
+    return false;
+  }
   auto hmac_check =
-    TpmHmac(
-        resource_manager_,
-        key->get(),
-        TpmAuth(ESYS_TR_PASSWORD),
-        signed_data.get(),
-        signed_data_size);
+      TpmHmac(resource_manager_, key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              maced_data->data(), maced_data->size());
   if (!hmac_check) {
     LOG(ERROR) << "Unable to calculate signature check";
     return false;
@@ -125,3 +126,22 @@
   return wrapped_->Deserialize(
       const_cast<const uint8_t**>(&inner_buf), inner_buf_end);
 }
+
+std::optional<std::vector<uint8_t>> HmacSerializable::AppendAad(
+    const uint8_t* sensitive, size_t sensitive_size) const {
+  if (!aad_) {
+    return std::vector<uint8_t>(sensitive, sensitive + sensitive_size);
+  }
+  std::vector<uint8_t> output(sensitive_size + aad_->SerializedSize());
+  std::copy(sensitive, sensitive + sensitive_size, output.begin());
+
+  const uint8_t* actual_output_end =
+      aad_->Serialize(&output[sensitive_size], output.data() + output.size());
+  const ptrdiff_t actual_aad_size = actual_output_end - output.data();
+  if (actual_aad_size != output.size()) {
+    LOG(ERROR) << "Serialized aad did not match expected size. Expected: "
+               << output.size() << ", actual: " << actual_aad_size;
+    return std::nullopt;
+  }
+  return output;
+}
diff --git a/host/commands/secure_env/hmac_serializable.h b/host/commands/secure_env/hmac_serializable.h
index 4b90124..8de17df 100644
--- a/host/commands/secure_env/hmac_serializable.h
+++ b/host/commands/secure_env/hmac_serializable.h
@@ -15,6 +15,9 @@
 
 #pragma once
 
+#include <optional>
+#include <vector>
+
 #include <keymaster/serializable.h>
 
 #include "host/commands/secure_env/tpm_resource_manager.h"
@@ -38,17 +41,21 @@
  */
 class HmacSerializable : public keymaster::Serializable {
 public:
-  HmacSerializable(TpmResourceManager&,
-                   std::function<TpmObjectSlot(TpmResourceManager&)>,
-                   uint32_t digest_size,
-                   Serializable*);
+ HmacSerializable(TpmResourceManager&,
+                  std::function<TpmObjectSlot(TpmResourceManager&)>,
+                  uint32_t digest_size, Serializable*, const Serializable* aad);
 
-  size_t SerializedSize() const override;
-  uint8_t* Serialize(uint8_t* buf, const uint8_t* end) const override;
-  bool Deserialize(const uint8_t** buf_ptr, const uint8_t* end) override;
+ size_t SerializedSize() const override;
+ uint8_t* Serialize(uint8_t* buf, const uint8_t* end) const override;
+ bool Deserialize(const uint8_t** buf_ptr, const uint8_t* end) override;
+
 private:
   TpmResourceManager& resource_manager_;
   std::function<TpmObjectSlot(TpmResourceManager&)> signing_key_fn_;
   uint32_t digest_size_;
-  keymaster::Serializable* wrapped_;
+  Serializable* wrapped_;
+  const Serializable* aad_;
+
+  std::optional<std::vector<uint8_t>> AppendAad(const uint8_t* sensitive,
+                                                size_t sensitive_size) const;
 };
diff --git a/host/commands/secure_env/json_serializable.cpp b/host/commands/secure_env/json_serializable.cpp
index d4f2fd8..bcca9f0 100644
--- a/host/commands/secure_env/json_serializable.cpp
+++ b/host/commands/secure_env/json_serializable.cpp
@@ -89,8 +89,9 @@
   EncryptedSerializable encryption(
       resource_manager, parent_key_fn, sensitive_material);
   auto signing_key_fn = SigningKeyCreator(kUniqueKey);
-  HmacSerializable sign_check(
-      resource_manager, signing_key_fn, TPM2_SHA256_DIGEST_SIZE, &encryption);
+  HmacSerializable sign_check(resource_manager, signing_key_fn,
+                              TPM2_SHA256_DIGEST_SIZE, &encryption,
+                              /*aad=*/nullptr);
 
   auto size = sign_check.SerializedSize();
   LOG(INFO) << "size : " << size;
@@ -139,8 +140,9 @@
   EncryptedSerializable encryption(
       resource_manager, parent_key_fn, sensitive_material);
   auto signing_key_fn = SigningKeyCreator(kUniqueKey);
-  HmacSerializable sign_check(
-      resource_manager, signing_key_fn, TPM2_SHA256_DIGEST_SIZE, &encryption);
+  HmacSerializable sign_check(resource_manager, signing_key_fn,
+                              TPM2_SHA256_DIGEST_SIZE, &encryption,
+                              /*aad=*/nullptr);
 
   auto buf = reinterpret_cast<const uint8_t*>(buffer.data());
   auto buf_end = buf + buffer.size();
diff --git a/host/commands/secure_env/keymaster_responder.cpp b/host/commands/secure_env/keymaster_responder.cpp
index 013d5e7..4936f7c 100644
--- a/host/commands/secure_env/keymaster_responder.cpp
+++ b/host/commands/secure_env/keymaster_responder.cpp
@@ -65,6 +65,9 @@
     HANDLE_MESSAGE(DELETE_KEY, DeleteKey)
     HANDLE_MESSAGE(DELETE_ALL_KEYS, DeleteAllKeys)
     HANDLE_MESSAGE(IMPORT_WRAPPED_KEY, ImportWrappedKey)
+    HANDLE_MESSAGE(GENERATE_RKP_KEY, GenerateRkpKey)
+    HANDLE_MESSAGE(GENERATE_CSR, GenerateCsr)
+    HANDLE_MESSAGE(GENERATE_TIMESTAMP_TOKEN, GenerateTimestampToken)
 #undef HANDLE_MESSAGE
 #define HANDLE_MESSAGE_W_RETURN(ENUM_NAME, METHOD_NAME) \
     case ENUM_NAME: {\
@@ -80,7 +83,10 @@
     HANDLE_MESSAGE_W_RETURN(VERIFY_AUTHORIZATION, VerifyAuthorization)
     HANDLE_MESSAGE_W_RETURN(DEVICE_LOCKED, DeviceLocked)
     HANDLE_MESSAGE_W_RETURN(GET_VERSION_2, GetVersion2)
-#undef HANDLE_MESSAGE
+    HANDLE_MESSAGE_W_RETURN(CONFIGURE_VENDOR_PATCHLEVEL,
+                            ConfigureVendorPatchlevel)
+    HANDLE_MESSAGE_W_RETURN(CONFIGURE_BOOT_PATCHLEVEL, ConfigureBootPatchlevel)
+#undef HANDLE_MESSAGE_W_RETURN
 #define HANDLE_MESSAGE_W_RETURN_NO_ARG(ENUM_NAME, METHOD_NAME) \
     case ENUM_NAME: {\
       auto response = keymaster_.METHOD_NAME(); \
@@ -88,7 +94,7 @@
     }
     HANDLE_MESSAGE_W_RETURN_NO_ARG(GET_HMAC_SHARING_PARAMETERS, GetHmacSharingParameters)
     HANDLE_MESSAGE_W_RETURN_NO_ARG(EARLY_BOOT_ENDED, EarlyBootEnded)
-#undef HANDLE_MESSAGE
+#undef HANDLE_MESSAGE_W_RETURN_NO_ARG
     case ADD_RNG_ENTROPY: {
       AddEntropyRequest request(keymaster_.message_version());
       if (!request.Deserialize(&buffer, end)) {
diff --git a/host/commands/secure_env/secure_env.cpp b/host/commands/secure_env/secure_env.cpp
index 7263404..9baaf15 100644
--- a/host/commands/secure_env/secure_env.cpp
+++ b/host/commands/secure_env/secure_env.cpp
@@ -26,11 +26,13 @@
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/security/gatekeeper_channel.h"
 #include "common/libs/security/keymaster_channel.h"
+#include "host/commands/kernel_log_monitor/kernel_log_server.h"
+#include "host/commands/kernel_log_monitor/utils.h"
 #include "host/commands/secure_env/device_tpm.h"
 #include "host/commands/secure_env/fragile_tpm_storage.h"
 #include "host/commands/secure_env/gatekeeper_responder.h"
-#include "host/commands/secure_env/insecure_fallback_storage.h"
 #include "host/commands/secure_env/in_process_tpm.h"
+#include "host/commands/secure_env/insecure_fallback_storage.h"
 #include "host/commands/secure_env/keymaster_responder.h"
 #include "host/commands/secure_env/soft_gatekeeper.h"
 #include "host/commands/secure_env/tpm_gatekeeper.h"
@@ -46,6 +48,11 @@
 DEFINE_int32(keymaster_fd_out, -1, "A pipe for keymaster communication");
 DEFINE_int32(gatekeeper_fd_in, -1, "A pipe for gatekeeper communication");
 DEFINE_int32(gatekeeper_fd_out, -1, "A pipe for gatekeeper communication");
+DEFINE_int32(kernel_events_fd, -1,
+             "A pipe for monitoring events based on "
+             "messages written to the kernel log. This "
+             "is used by secure_env to monitor for "
+             "device reboots.");
 
 DEFINE_string(tpm_impl,
               "in_memory",
@@ -57,6 +64,51 @@
 DEFINE_string(gatekeeper_impl, "tpm",
               "The gatekeeper implementation. \"tpm\" or \"software\"");
 
+namespace {
+
+// Dup a command line file descriptor into a SharedFD.
+cuttlefish::SharedFD DupFdFlag(gflags::int32 fd) {
+  CHECK(fd != -1);
+  cuttlefish::SharedFD duped = cuttlefish::SharedFD::Dup(fd);
+  CHECK(duped->IsOpen()) << "Could not dup output fd: " << duped->StrError();
+  // The original FD is intentionally kept open so that we can re-exec this
+  // process without having to do a bunch of argv book-keeping.
+  return duped;
+}
+
+// Re-launch this process with all the same flags it was originallys started
+// with.
+[[noreturn]] void ReExecSelf() {
+  // Allocate +1 entry for terminating nullptr.
+  std::vector<char*> argv(gflags::GetArgvs().size() + 1, nullptr);
+  for (size_t i = 0; i < gflags::GetArgvs().size(); ++i) {
+    argv[i] = strdup(gflags::GetArgvs()[i].c_str());
+    CHECK(argv[i] != nullptr) << "OOM";
+  }
+  execv("/proc/self/exe", argv.data());
+  char buf[128];
+  LOG(FATAL) << "Exec failed, secure_env is out of sync with the guest: "
+             << errno << "(" << strerror_r(errno, buf, sizeof(buf)) << ")";
+  abort();  // LOG(FATAL) isn't marked as noreturn
+}
+
+// Spin up a thread that monitors for a kernel loaded event, then re-execs
+// this process. This way, secure_env's boot tracking matches up with the guest.
+std::thread StartKernelEventMonitor(cuttlefish::SharedFD kernel_events_fd) {
+  return std::thread([kernel_events_fd]() {
+    while (kernel_events_fd->IsOpen()) {
+      auto read_result = monitor::ReadEvent(kernel_events_fd);
+      CHECK(read_result.has_value()) << kernel_events_fd->StrError();
+      if (read_result->event == monitor::Event::KernelLoaded) {
+        LOG(DEBUG) << "secure_env detected guest reboot, restarting.";
+        ReExecSelf();
+      }
+    }
+  });
+}
+
+}  // namespace
+
 int main(int argc, char** argv) {
   cuttlefish::DefaultSubprocessLogging(argv);
   gflags::ParseCommandLineFlags(&argc, &argv, true);
@@ -114,9 +166,8 @@
   keymaster::KeymasterContext* keymaster_context;
   if (FLAGS_keymint_impl == "software") {
     // TODO: See if this is the right KM version.
-    keymaster_context =
-        new keymaster::PureSoftKeymasterContext(keymaster::KmVersion::KEYMASTER_4,
-                                                KM_SECURITY_LEVEL_SOFTWARE);
+    keymaster_context = new keymaster::PureSoftKeymasterContext(
+        keymaster::KmVersion::KEYMINT_1, KM_SECURITY_LEVEL_SOFTWARE);
   } else if (FLAGS_keymint_impl == "tpm") {
     keymaster_context =
         new TpmKeymasterContext(*resource_manager, *keymaster_enforcement);
@@ -125,33 +176,19 @@
     return -1;
   }
   keymaster::AndroidKeymaster keymaster{
-      keymaster_context, kOperationTableSize};
+      keymaster_context, kOperationTableSize,
+      keymaster::MessageVersion(keymaster::KmVersion::KEYMINT_1,
+                                0 /* km_date */)};
 
-  CHECK(FLAGS_keymaster_fd_in != -1);
-  auto keymaster_in = cuttlefish::SharedFD::Dup(FLAGS_keymaster_fd_in);
-  CHECK(keymaster_in->IsOpen()) << "Could not dup input fd: "
-                                << keymaster_in->StrError();
-  close(FLAGS_keymaster_fd_in);
+  auto keymaster_in = DupFdFlag(FLAGS_keymaster_fd_in);
+  auto keymaster_out = DupFdFlag(FLAGS_keymaster_fd_out);
+  auto gatekeeper_in = DupFdFlag(FLAGS_gatekeeper_fd_in);
+  auto gatekeeper_out = DupFdFlag(FLAGS_gatekeeper_fd_out);
+  auto kernel_events_fd = DupFdFlag(FLAGS_kernel_events_fd);
 
-  CHECK(FLAGS_keymaster_fd_out != -1);
-  auto keymaster_out = cuttlefish::SharedFD::Dup(FLAGS_keymaster_fd_out);
-  CHECK(keymaster_out->IsOpen()) << "Could not dup output fd: "
-                                 << keymaster_out->StrError();
-  close(FLAGS_keymaster_fd_out);
+  std::vector<std::thread> threads;
 
-  CHECK(FLAGS_gatekeeper_fd_in != -1);
-  auto gatekeeper_in = cuttlefish::SharedFD::Dup(FLAGS_gatekeeper_fd_in);
-  CHECK(gatekeeper_in->IsOpen()) << "Could not dup input fd: "
-                                << gatekeeper_in->StrError();
-  close(FLAGS_gatekeeper_fd_in);
-
-  CHECK(FLAGS_gatekeeper_fd_out != -1);
-  auto gatekeeper_out = cuttlefish::SharedFD::Dup(FLAGS_gatekeeper_fd_out);
-  CHECK(gatekeeper_out->IsOpen()) << "Could not dup output fd: "
-                                  << keymaster_out->StrError();
-  close(FLAGS_gatekeeper_fd_out);
-
-  std::thread keymaster_thread([keymaster_in, keymaster_out, &keymaster]() {
+  threads.emplace_back([keymaster_in, keymaster_out, &keymaster]() {
     while (true) {
       cuttlefish::KeymasterChannel keymaster_channel(
           keymaster_in, keymaster_out);
@@ -163,7 +200,7 @@
     }
   });
 
-  std::thread gatekeeper_thread([gatekeeper_in, gatekeeper_out, &gatekeeper]() {
+  threads.emplace_back([gatekeeper_in, gatekeeper_out, &gatekeeper]() {
     while (true) {
       cuttlefish::GatekeeperChannel gatekeeper_channel(
           gatekeeper_in, gatekeeper_out);
@@ -175,6 +212,9 @@
     }
   });
 
-  keymaster_thread.join();
-  gatekeeper_thread.join();
+  threads.emplace_back(StartKernelEventMonitor(kernel_events_fd));
+
+  for (auto& t : threads) {
+    t.join();
+  }
 }
diff --git a/host/commands/secure_env/tpm_attestation_record.cpp b/host/commands/secure_env/tpm_attestation_record.cpp
index f47a0ef..1a26422 100644
--- a/host/commands/secure_env/tpm_attestation_record.cpp
+++ b/host/commands/secure_env/tpm_attestation_record.cpp
@@ -19,22 +19,37 @@
 
 #include <android-base/logging.h>
 
+namespace {
+using VerifiedBootParams = keymaster::AttestationContext::VerifiedBootParams;
 using keymaster::AuthorizationSet;
 
+VerifiedBootParams MakeVbParams() {
+  // Cuttlefish is hard-coded to verifiedbootstate=orange
+  // See device/google/cuttlefish/host/libs/config/bootconfig_args.cpp
+  VerifiedBootParams vb_params;
+  static uint8_t empty_vb_key[32] = {};
+  vb_params.verified_boot_key = {empty_vb_key, sizeof(empty_vb_key)};
+  vb_params.verified_boot_hash = {empty_vb_key, sizeof(empty_vb_key)};
+  vb_params.verified_boot_state = KM_VERIFIED_BOOT_UNVERIFIED;
+  vb_params.device_locked = false;
+  return vb_params;
+}
+
+}  // namespace
+
+TpmAttestationRecordContext::TpmAttestationRecordContext()
+    : keymaster::AttestationContext(::keymaster::KmVersion::KEYMINT_1),
+      vb_params_(MakeVbParams()) {}
+
 keymaster_security_level_t TpmAttestationRecordContext::GetSecurityLevel() const {
   return KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT;
 }
 
 keymaster_error_t TpmAttestationRecordContext::VerifyAndCopyDeviceIds(
-    const AuthorizationSet& attestation_params,
-    AuthorizationSet* attestation) const {
+    const AuthorizationSet& /*attestation_params*/,
+    AuthorizationSet* /*attestation*/) const {
   LOG(DEBUG) << "TODO(schuffelen): Implement VerifyAndCopyDeviceIds";
-  attestation->Difference(attestation_params);
-  attestation->Union(attestation_params);
-  if (int index = attestation->find(keymaster::TAG_ATTESTATION_APPLICATION_ID)) {
-    attestation->erase(index);
-  }
-  return KM_ERROR_OK;
+  return KM_ERROR_UNIMPLEMENTED;
 }
 
 keymaster::Buffer TpmAttestationRecordContext::GenerateUniqueId(
@@ -44,28 +59,11 @@
   return {};
 }
 
-const keymaster::AttestationContext::VerifiedBootParams*
-TpmAttestationRecordContext::GetVerifiedBootParams(keymaster_error_t* error) const {
-  LOG(DEBUG) << "TODO(schuffelen): Implement GetVerifiedBootParams";
-  if (!vb_params_) {
-      vb_params_.reset(new VerifiedBootParams{});
-
-      // TODO(schuffelen): Get this data out of vbmeta
-      static uint8_t fake_vb_key[32];
-      static bool fake_vb_key_initialized = false;
-      if (!fake_vb_key_initialized) {
-        for (int i = 0; i < sizeof(fake_vb_key); i++) {
-          fake_vb_key[i] = rand();
-        }
-        fake_vb_key_initialized = true;
-      }
-      vb_params_->verified_boot_key = {fake_vb_key, sizeof(fake_vb_key)};
-      vb_params_->verified_boot_hash = {fake_vb_key, sizeof(fake_vb_key)};
-      vb_params_->verified_boot_state = KM_VERIFIED_BOOT_VERIFIED;
-      vb_params_->device_locked = true;
-  }
+const VerifiedBootParams* TpmAttestationRecordContext::GetVerifiedBootParams(
+    keymaster_error_t* error) const {
+  static VerifiedBootParams vb_params = MakeVbParams();
   *error = KM_ERROR_OK;
-  return vb_params_.get();
+  return &vb_params;
 }
 
 keymaster::KeymasterKeyBlob
diff --git a/host/commands/secure_env/tpm_attestation_record.h b/host/commands/secure_env/tpm_attestation_record.h
index d5d9b98..c65eef4 100644
--- a/host/commands/secure_env/tpm_attestation_record.h
+++ b/host/commands/secure_env/tpm_attestation_record.h
@@ -21,24 +21,21 @@
 
 class TpmAttestationRecordContext : public keymaster::AttestationContext {
 public:
-  TpmAttestationRecordContext() : keymaster::AttestationContext(
-      ::keymaster::KmVersion::KEYMASTER_4_1) {}
-  ~TpmAttestationRecordContext() = default;
+ TpmAttestationRecordContext();
+ ~TpmAttestationRecordContext() = default;
 
-  keymaster_security_level_t GetSecurityLevel() const override;
-  keymaster_error_t VerifyAndCopyDeviceIds(
-      const keymaster::AuthorizationSet&,
-      keymaster::AuthorizationSet*) const override;
-  keymaster::Buffer GenerateUniqueId(
-      uint64_t, const keymaster_blob_t&, bool, keymaster_error_t*) const override;
-  const VerifiedBootParams* GetVerifiedBootParams(
-      keymaster_error_t* error) const override;
-  keymaster::KeymasterKeyBlob GetAttestationKey(
-      keymaster_algorithm_t algorithm, keymaster_error_t* error) const override;
-  keymaster::CertificateChain GetAttestationChain(
-      keymaster_algorithm_t algorithm,
-      keymaster_error_t* error) const override;
-
+ keymaster_security_level_t GetSecurityLevel() const override;
+ keymaster_error_t VerifyAndCopyDeviceIds(
+     const keymaster::AuthorizationSet&,
+     keymaster::AuthorizationSet*) const override;
+ keymaster::Buffer GenerateUniqueId(uint64_t, const keymaster_blob_t&, bool,
+                                    keymaster_error_t*) const override;
+ const VerifiedBootParams* GetVerifiedBootParams(
+     keymaster_error_t* error) const override;
+ keymaster::KeymasterKeyBlob GetAttestationKey(
+     keymaster_algorithm_t algorithm, keymaster_error_t* error) const override;
+ keymaster::CertificateChain GetAttestationChain(
+     keymaster_algorithm_t algorithm, keymaster_error_t* error) const override;
 private:
-    mutable std::unique_ptr<VerifiedBootParams> vb_params_;
+ VerifiedBootParams vb_params_;
 };
diff --git a/host/commands/secure_env/tpm_key_blob_maker.cpp b/host/commands/secure_env/tpm_key_blob_maker.cpp
index 64f344a..a27ea76 100644
--- a/host/commands/secure_env/tpm_key_blob_maker.cpp
+++ b/host/commands/secure_env/tpm_key_blob_maker.cpp
@@ -41,9 +41,76 @@
 static keymaster_error_t SplitEnforcedProperties(
     const keymaster::AuthorizationSet& key_description,
     keymaster::AuthorizationSet* hw_enforced,
-    keymaster::AuthorizationSet* sw_enforced) {
+    keymaster::AuthorizationSet* sw_enforced,
+    keymaster::AuthorizationSet* hidden) {
   for (auto& entry : key_description) {
     switch (entry.tag) {
+      // These cannot be specified by the client.
+      case KM_TAG_BOOT_PATCHLEVEL:
+      case KM_TAG_ORIGIN:
+      case KM_TAG_OS_PATCHLEVEL:
+      case KM_TAG_OS_VERSION:
+      case KM_TAG_ROOT_OF_TRUST:
+      case KM_TAG_VENDOR_PATCHLEVEL:
+        LOG(ERROR) << "Root of trust and origin tags may not be specified";
+        return KM_ERROR_INVALID_TAG;
+
+      // These are hidden
+      case KM_TAG_APPLICATION_DATA:
+      case KM_TAG_APPLICATION_ID:
+        hidden->push_back(entry);
+        break;
+
+      // These should not be in key descriptions because they're for operation
+      // parameters.
+      case KM_TAG_ASSOCIATED_DATA:
+      case KM_TAG_AUTH_TOKEN:
+      case KM_TAG_CONFIRMATION_TOKEN:
+      case KM_TAG_INVALID:
+      case KM_TAG_MAC_LENGTH:
+      case KM_TAG_NONCE:
+        LOG(ERROR) << "Tag " << entry.tag
+                   << " not allowed in key generation/import";
+        break;
+
+      // These are provided to support attestation key generation, but should
+      // not be included in the key characteristics.
+      case KM_TAG_ATTESTATION_APPLICATION_ID:
+      case KM_TAG_ATTESTATION_CHALLENGE:
+      case KM_TAG_ATTESTATION_ID_BRAND:
+      case KM_TAG_ATTESTATION_ID_DEVICE:
+      case KM_TAG_ATTESTATION_ID_IMEI:
+      case KM_TAG_ATTESTATION_ID_MANUFACTURER:
+      case KM_TAG_ATTESTATION_ID_MEID:
+      case KM_TAG_ATTESTATION_ID_MODEL:
+      case KM_TAG_ATTESTATION_ID_PRODUCT:
+      case KM_TAG_ATTESTATION_ID_SERIAL:
+      case KM_TAG_CERTIFICATE_SERIAL:
+      case KM_TAG_CERTIFICATE_SUBJECT:
+      case KM_TAG_CERTIFICATE_NOT_BEFORE:
+      case KM_TAG_CERTIFICATE_NOT_AFTER:
+      case KM_TAG_RESET_SINCE_ID_ROTATION:
+        break;
+
+      // strongbox-only tags
+      case KM_TAG_DEVICE_UNIQUE_ATTESTATION:
+        LOG(ERROR) << "Strongbox-only tag: " << entry.tag;
+        return KM_ERROR_UNSUPPORTED_TAG;
+
+      case KM_TAG_ROLLBACK_RESISTANT:
+        return KM_ERROR_UNSUPPORTED_TAG;
+
+      case KM_TAG_ROLLBACK_RESISTANCE:
+        LOG(ERROR) << "Rollback resistance is not implemented.";
+        return KM_ERROR_ROLLBACK_RESISTANCE_UNAVAILABLE;
+
+      // These are nominally HW tags, but we don't actually support HW key
+      // attestation yet.
+      case KM_TAG_ALLOW_WHILE_ON_BODY:
+      case KM_TAG_EXPORTABLE:
+      case KM_TAG_IDENTITY_CREDENTIAL_KEY:
+      case KM_TAG_STORAGE_KEY:
+
       case KM_TAG_PURPOSE:
       case KM_TAG_ALGORITHM:
       case KM_TAG_KEY_SIZE:
@@ -63,17 +130,32 @@
       case KM_TAG_EC_CURVE:
       case KM_TAG_ECIES_SINGLE_HASH_MODE:
       case KM_TAG_USER_AUTH_TYPE:
-      case KM_TAG_ORIGIN:
-      case KM_TAG_OS_VERSION:
-      case KM_TAG_OS_PATCHLEVEL:
       case KM_TAG_EARLY_BOOT_ONLY:
       case KM_TAG_UNLOCKED_DEVICE_REQUIRED:
         hw_enforced->push_back(entry);
         break;
-      default:
+
+      // The remaining tags are all software.
+      case KM_TAG_ACTIVE_DATETIME:
+      case KM_TAG_ALL_APPLICATIONS:
+      case KM_TAG_ALL_USERS:
+      case KM_TAG_BOOTLOADER_ONLY:
+      case KM_TAG_CREATION_DATETIME:
+      case KM_TAG_INCLUDE_UNIQUE_ID:
+      case KM_TAG_MAX_BOOT_LEVEL:
+      case KM_TAG_ORIGINATION_EXPIRE_DATETIME:
+      case KM_TAG_RSA_OAEP_MGF_DIGEST:
+      case KM_TAG_TRUSTED_CONFIRMATION_REQUIRED:
+      case KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED:
+      case KM_TAG_UNIQUE_ID:
+      case KM_TAG_USAGE_COUNT_LIMIT:
+      case KM_TAG_USAGE_EXPIRE_DATETIME:
+      case KM_TAG_USER_ID:
         sw_enforced->push_back(entry);
+        break;
     }
   }
+
   return KM_ERROR_OK;
 }
 
@@ -102,20 +184,9 @@
     KeymasterKeyBlob* blob,
     AuthorizationSet* hw_enforced,
     AuthorizationSet* sw_enforced) const {
-  std::set<keymaster_tag_t> protected_tags = {
-    KM_TAG_ROOT_OF_TRUST,
-    KM_TAG_ORIGIN,
-    KM_TAG_OS_VERSION,
-    KM_TAG_OS_PATCHLEVEL,
-  };
-  for (auto tag : protected_tags) {
-    if (key_description.Contains(tag)) {
-      LOG(ERROR) << "Invalid tag " << tag;
-      return KM_ERROR_INVALID_TAG;
-    }
-  }
-  auto rc =
-      SplitEnforcedProperties(key_description, hw_enforced, sw_enforced);
+  AuthorizationSet hidden;
+  auto rc = SplitEnforcedProperties(key_description, hw_enforced, sw_enforced,
+                                    &hidden);
   if (rc != KM_ERROR_OK) {
     return rc;
   }
@@ -126,12 +197,13 @@
   hw_enforced->push_back(keymaster::TAG_OS_PATCHLEVEL, os_patchlevel_);
 
   return UnvalidatedCreateKeyBlob(key_material, *hw_enforced, *sw_enforced,
-                                  blob);
+                                  hidden, blob);
 }
 
 keymaster_error_t TpmKeyBlobMaker::UnvalidatedCreateKeyBlob(
     const KeymasterKeyBlob& key_material, const AuthorizationSet& hw_enforced,
-    const AuthorizationSet& sw_enforced, KeymasterKeyBlob* blob) const {
+    const AuthorizationSet& sw_enforced, const AuthorizationSet& hidden,
+    KeymasterKeyBlob* blob) const {
   keymaster::Buffer key_material_buffer(
       key_material.key_material, key_material.key_material_size);
   AuthorizationSet hw_enforced_mutable = hw_enforced;
@@ -142,8 +214,12 @@
   EncryptedSerializable encryption(
       resource_manager_, parent_key_fn, sensitive_material);
   auto signing_key_fn = SigningKeyCreator(kUniqueKey);
-  HmacSerializable sign_check(
-      resource_manager_, signing_key_fn, TPM2_SHA256_DIGEST_SIZE, &encryption);
+  // TODO(b/154956668) The "hidden" tags should also be mixed into the TPM ACL
+  // so that the TPM requires them to be presented to unwrap the key. This is
+  // necessary to meet the requirement that full breach of KeyMint means an
+  // attacker cannot unwrap keys w/o the application id/data.
+  HmacSerializable sign_check(resource_manager_, signing_key_fn,
+                              TPM2_SHA256_DIGEST_SIZE, &encryption, &hidden);
   auto generated_blob = SerializableToKeyBlob(sign_check);
   LOG(VERBOSE) << "Keymaster key size: " << generated_blob.key_material_size;
   if (generated_blob.key_material_size != 0) {
@@ -155,9 +231,8 @@
 }
 
 keymaster_error_t TpmKeyBlobMaker::UnwrapKeyBlob(
-    const keymaster_key_blob_t& blob,
-    AuthorizationSet* hw_enforced,
-    AuthorizationSet* sw_enforced,
+    const keymaster_key_blob_t& blob, AuthorizationSet* hw_enforced,
+    AuthorizationSet* sw_enforced, const AuthorizationSet& hidden,
     KeymasterKeyBlob* key_material) const {
   keymaster::Buffer key_material_buffer(blob.key_material_size);
   CompositeSerializable sensitive_material(
@@ -166,17 +241,17 @@
   EncryptedSerializable encryption(
       resource_manager_, parent_key_fn, sensitive_material);
   auto signing_key_fn = SigningKeyCreator(kUniqueKey);
-  HmacSerializable sign_check(
-      resource_manager_, signing_key_fn, TPM2_SHA256_DIGEST_SIZE, &encryption);
+  HmacSerializable sign_check(resource_manager_, signing_key_fn,
+                              TPM2_SHA256_DIGEST_SIZE, &encryption, &hidden);
   auto buf = blob.key_material;
   auto buf_end = buf + blob.key_material_size;
   if (!sign_check.Deserialize(&buf, buf_end)) {
     LOG(ERROR) << "Failed to deserialize key.";
-    return KM_ERROR_UNKNOWN_ERROR;
+    return KM_ERROR_INVALID_KEY_BLOB;
   }
   if (key_material_buffer.available_read() == 0) {
     LOG(ERROR) << "Key material was corrupted and the size was too large";
-    return KM_ERROR_UNKNOWN_ERROR;
+    return KM_ERROR_INVALID_KEY_BLOB;
   }
   *key_material = KeymasterKeyBlob(
       key_material_buffer.peek_read(), key_material_buffer.available_read());
diff --git a/host/commands/secure_env/tpm_key_blob_maker.h b/host/commands/secure_env/tpm_key_blob_maker.h
index a0483b4..736e696 100644
--- a/host/commands/secure_env/tpm_key_blob_maker.h
+++ b/host/commands/secure_env/tpm_key_blob_maker.h
@@ -43,6 +43,7 @@
       const keymaster::KeymasterKeyBlob& key_material,
       const keymaster::AuthorizationSet& hw_enforced,
       const keymaster::AuthorizationSet& sw_enforced,
+      const keymaster::AuthorizationSet& hidden,
       keymaster::KeymasterKeyBlob* blob) const;
 
   /**
@@ -60,6 +61,7 @@
       const keymaster_key_blob_t& blob,
       keymaster::AuthorizationSet* hw_enforced,
       keymaster::AuthorizationSet* sw_enforced,
+      const keymaster::AuthorizationSet& hidden,
       keymaster::KeymasterKeyBlob* key_material) const;
 
   keymaster_error_t SetSystemVersion(uint32_t os_version, uint32_t os_patchlevel);
diff --git a/host/commands/secure_env/tpm_keymaster_context.cpp b/host/commands/secure_env/tpm_keymaster_context.cpp
index 850d48b..1a240d3 100644
--- a/host/commands/secure_env/tpm_keymaster_context.cpp
+++ b/host/commands/secure_env/tpm_keymaster_context.cpp
@@ -28,22 +28,43 @@
 #include <keymaster/km_openssl/triple_des_key.h>
 
 #include "host/commands/secure_env/tpm_attestation_record.h"
-#include "host/commands/secure_env/tpm_random_source.h"
 #include "host/commands/secure_env/tpm_key_blob_maker.h"
+#include "host/commands/secure_env/tpm_random_source.h"
+#include "host/commands/secure_env/tpm_remote_provisioning_context.h"
 
+namespace {
 using keymaster::AuthorizationSet;
 using keymaster::KeymasterKeyBlob;
 using keymaster::KeyFactory;
 using keymaster::OperationFactory;
 
+keymaster::AuthorizationSet GetHiddenTags(
+    const AuthorizationSet& authorizations) {
+  keymaster::AuthorizationSet output;
+  keymaster_blob_t entry;
+  if (authorizations.GetTagValue(keymaster::TAG_APPLICATION_ID, &entry)) {
+    output.push_back(keymaster::TAG_APPLICATION_ID, entry.data,
+                     entry.data_length);
+  }
+  if (authorizations.GetTagValue(keymaster::TAG_APPLICATION_DATA, &entry)) {
+    output.push_back(keymaster::TAG_APPLICATION_DATA, entry.data,
+                     entry.data_length);
+  }
+  return output;
+}
+
+}  // namespace
+
 TpmKeymasterContext::TpmKeymasterContext(
     TpmResourceManager& resource_manager,
     keymaster::KeymasterEnforcement& enforcement)
-    : resource_manager_(resource_manager)
-    , enforcement_(enforcement)
-    , key_blob_maker_(new TpmKeyBlobMaker(resource_manager_))
-    , random_source_(new TpmRandomSource(resource_manager_.Esys()))
-    , attestation_context_(new TpmAttestationRecordContext()) {
+    : resource_manager_(resource_manager),
+      enforcement_(enforcement),
+      key_blob_maker_(new TpmKeyBlobMaker(resource_manager_)),
+      random_source_(new TpmRandomSource(resource_manager_.Esys())),
+      attestation_context_(new TpmAttestationRecordContext()),
+      remote_provisioning_context_(
+          new TpmRemoteProvisioningContext(resource_manager_)) {
   key_factories_.emplace(
       KM_ALGORITHM_RSA, new keymaster::RsaKeyFactory(*key_blob_maker_, *this));
   key_factories_.emplace(
@@ -187,7 +208,7 @@
 
   return key_blob_maker_->UnvalidatedCreateKeyBlob(
       key->key_material(), key->hw_enforced(), key->sw_enforced(),
-      upgraded_key);
+      GetHiddenTags(upgrade_params), upgraded_key);
 }
 
 keymaster_error_t TpmKeymasterContext::ParseKeyBlob(
@@ -198,12 +219,10 @@
   keymaster::AuthorizationSet sw_enforced;
   keymaster::KeymasterKeyBlob key_material;
 
-  auto rc =
-      key_blob_maker_->UnwrapKeyBlob(
-          blob,
-          &hw_enforced,
-          &sw_enforced,
-          &key_material);
+  keymaster::AuthorizationSet hidden = GetHiddenTags(additional_params);
+
+  auto rc = key_blob_maker_->UnwrapKeyBlob(blob, &hw_enforced, &sw_enforced,
+                                           hidden, &key_material);
   if (rc != KM_ERROR_OK) {
     LOG(ERROR) << "Failed to unwrap key: " << rc;
     return rc;
@@ -247,18 +266,20 @@
 
 keymaster::CertificateChain TpmKeymasterContext::GenerateAttestation(
     const keymaster::Key& key, const keymaster::AuthorizationSet& attest_params,
-    keymaster::UniquePtr<keymaster::Key> /* attest_key */,
-    const keymaster::KeymasterBlob& /* issuer_subject */,
+    keymaster::UniquePtr<keymaster::Key> attest_key,
+    const keymaster::KeymasterBlob& issuer_subject,
     keymaster_error_t* error) const {
   LOG(INFO) << "TODO(b/155697200): Link attestation back to the TPM";
   keymaster_algorithm_t key_algorithm;
   if (!key.authorizations().GetTagValue(keymaster::TAG_ALGORITHM,
                                         &key_algorithm)) {
+    LOG(ERROR) << "Cannot find key algorithm (TAG_ALGORITHM)";
     *error = KM_ERROR_UNKNOWN_ERROR;
     return {};
   }
 
   if ((key_algorithm != KM_ALGORITHM_RSA && key_algorithm != KM_ALGORITHM_EC)) {
+    LOG(ERROR) << "Invalid algorithm: " << key_algorithm;
     *error = KM_ERROR_INCOMPATIBLE_ALGORITHM;
     return {};
   }
@@ -277,12 +298,20 @@
   // hardware/interfaces/keymaster/4.1/vts/functional/DeviceUniqueAttestationTest.cpp:203
   // at commit 36dcf1a404a9cf07ca5a2a6ad92371507194fe1b .
   if (attest_params.find(keymaster::TAG_DEVICE_UNIQUE_ATTESTATION) != -1) {
+    LOG(ERROR) << "TAG_DEVICE_UNIQUE_ATTESTATION not supported";
     *error = KM_ERROR_UNIMPLEMENTED;
     return {};
   }
 
+  keymaster::AttestKeyInfo attest_key_info(attest_key, &issuer_subject, error);
+  if (*error != KM_ERROR_OK) {
+    LOG(ERROR)
+        << "Error creating attestation key info from given key and subject";
+    return {};
+  }
+
   return keymaster::generate_attestation(asymmetric_key, attest_params,
-                                         {} /* attest_key */,
+                                         std::move(attest_key_info),
                                          *attestation_context_, error);
 }
 
@@ -319,3 +348,8 @@
   LOG(ERROR) << "TODO(b/155697375): Implement UnwrapKey";
   return KM_ERROR_UNIMPLEMENTED;
 }
+
+keymaster::RemoteProvisioningContext*
+TpmKeymasterContext::GetRemoteProvisioningContext() const {
+  return remote_provisioning_context_.get();
+}
diff --git a/host/commands/secure_env/tpm_keymaster_context.h b/host/commands/secure_env/tpm_keymaster_context.h
index 54ba973..30b069d 100644
--- a/host/commands/secure_env/tpm_keymaster_context.h
+++ b/host/commands/secure_env/tpm_keymaster_context.h
@@ -27,6 +27,7 @@
 class TpmResourceManager;
 class TpmKeyBlobMaker;
 class TpmRandomSource;
+class TpmRemoteProvisioningContext;
 
 /**
  * Implementation of KeymasterContext that wraps its keys with a TPM.
@@ -41,11 +42,15 @@
   std::unique_ptr<TpmKeyBlobMaker> key_blob_maker_;
   std::unique_ptr<TpmRandomSource> random_source_;
   std::unique_ptr<TpmAttestationRecordContext> attestation_context_;
+  std::unique_ptr<TpmRemoteProvisioningContext> remote_provisioning_context_;
   std::map<keymaster_algorithm_t, std::unique_ptr<keymaster::KeyFactory>> key_factories_;
   std::vector<keymaster_algorithm_t> supported_algorithms_;
   uint32_t os_version_;
   uint32_t os_patchlevel_;
-public:
+  std::optional<uint32_t> vendor_patchlevel_;
+  std::optional<uint32_t> boot_patchlevel_;
+
+ public:
   TpmKeymasterContext(TpmResourceManager&, keymaster::KeymasterEnforcement&);
   ~TpmKeymasterContext() = default;
 
@@ -102,4 +107,35 @@
       keymaster::AuthorizationSet* wrapped_key_params,
       keymaster_key_format_t* wrapped_key_format,
       keymaster::KeymasterKeyBlob* wrapped_key_material) const override;
+
+  keymaster::RemoteProvisioningContext* GetRemoteProvisioningContext()
+      const override;
+
+  keymaster_error_t SetVendorPatchlevel(uint32_t vendor_patchlevel) override {
+    if (vendor_patchlevel_.has_value() &&
+        vendor_patchlevel != vendor_patchlevel_.value()) {
+      // Can't set patchlevel to a different value.
+      return KM_ERROR_INVALID_ARGUMENT;
+    }
+    vendor_patchlevel_ = vendor_patchlevel;
+    return KM_ERROR_OK;
+  }
+
+  keymaster_error_t SetBootPatchlevel(uint32_t boot_patchlevel) override {
+    if (boot_patchlevel_.has_value() &&
+        boot_patchlevel != boot_patchlevel_.value()) {
+      // Can't set patchlevel to a different value.
+      return KM_ERROR_INVALID_ARGUMENT;
+    }
+    boot_patchlevel_ = boot_patchlevel;
+    return KM_ERROR_OK;
+  }
+
+  std::optional<uint32_t> GetVendorPatchlevel() const override {
+    return vendor_patchlevel_;
+  }
+
+  std::optional<uint32_t> GetBootPatchlevel() const override {
+    return boot_patchlevel_;
+  }
 };
diff --git a/host/commands/secure_env/tpm_keymaster_enforcement.cpp b/host/commands/secure_env/tpm_keymaster_enforcement.cpp
index f5b8903..fe47c07 100644
--- a/host/commands/secure_env/tpm_keymaster_enforcement.cpp
+++ b/host/commands/secure_env/tpm_keymaster_enforcement.cpp
@@ -294,18 +294,38 @@
   return response;
 }
 
+keymaster_error_t TpmKeymasterEnforcement::GenerateTimestampToken(
+    keymaster::TimestampToken* token) {
+  token->timestamp = get_current_time_ms();
+  token->security_level = SecurityLevel();
+  token->mac = KeymasterBlob();
+
+  auto signing_key_builder = PrimaryKeyBuilder();
+  signing_key_builder.SigningKey();
+  signing_key_builder.UniqueData("timestamp_token");
+  auto signing_key = signing_key_builder.CreateKey(resource_manager_);
+  if (!signing_key) {
+    LOG(ERROR) << "Could not make signing key for verifying authorization";
+    return KM_ERROR_UNKNOWN_ERROR;
+  }
+  std::vector<uint8_t> token_buf_to_sign(token->SerializedSize(), 0);
+  auto hmac =
+      TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              token_buf_to_sign.data(), token_buf_to_sign.size());
+  if (!hmac) {
+    LOG(ERROR) << "Could not calculate timestamp token hmac";
+    return KM_ERROR_UNKNOWN_ERROR;
+  } else if (hmac->size == 0) {
+    LOG(ERROR) << "hmac was too short";
+    return KM_ERROR_UNKNOWN_ERROR;
+  }
+  token->mac = KeymasterBlob(hmac->buffer, hmac->size);
+
+  return KM_ERROR_OK;
+}
+
 bool TpmKeymasterEnforcement::CreateKeyId(
     const keymaster_key_blob_t& key_blob, km_id_t* keyid) const {
-  keymaster::AuthorizationSet hw_enforced;
-  keymaster::AuthorizationSet sw_enforced;
-  keymaster::KeymasterKeyBlob key_material;
-  auto rc =
-      TpmKeyBlobMaker(resource_manager_)
-          .UnwrapKeyBlob(key_blob, &hw_enforced, &sw_enforced, &key_material);
-  if (rc != KM_ERROR_OK) {
-    LOG(ERROR) << "Could not unwrap key: " << rc;
-    return false;
-  }
   auto signing_key_builder = PrimaryKeyBuilder();
   signing_key_builder.SigningKey();
   signing_key_builder.UniqueData("key_id");
@@ -314,12 +334,9 @@
     LOG(ERROR) << "Could not make signing key for key id";
     return false;
   }
-  auto hmac = TpmHmac(
-      resource_manager_,
-      signing_key->get(),
-      TpmAuth(ESYS_TR_PASSWORD),
-      key_material.key_material,
-      key_material.key_material_size);
+  auto hmac =
+      TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              key_blob.key_material, key_blob.key_material_size);
   if (!hmac) {
     LOG(ERROR) << "Failed to make a signature for a key id";
     return false;
diff --git a/host/commands/secure_env/tpm_keymaster_enforcement.h b/host/commands/secure_env/tpm_keymaster_enforcement.h
index 1d6a1e5..3765c06 100644
--- a/host/commands/secure_env/tpm_keymaster_enforcement.h
+++ b/host/commands/secure_env/tpm_keymaster_enforcement.h
@@ -49,6 +49,9 @@
   keymaster::VerifyAuthorizationResponse VerifyAuthorization(
       const keymaster::VerifyAuthorizationRequest& request) override;
 
+  keymaster_error_t GenerateTimestampToken(
+      keymaster::TimestampToken* token) override;
+
   bool CreateKeyId(
       const keymaster_key_blob_t& key_blob,
       keymaster::km_id_t* keyid) const override;
diff --git a/host/commands/secure_env/tpm_random_source.cpp b/host/commands/secure_env/tpm_random_source.cpp
index 8695e4a..7849eba 100644
--- a/host/commands/secure_env/tpm_random_source.cpp
+++ b/host/commands/secure_env/tpm_random_source.cpp
@@ -62,6 +62,11 @@
 
 keymaster_error_t TpmRandomSource::AddRngEntropy(
     const uint8_t* buffer, size_t size) const {
+  if (size > 2048) {
+    // IKeyMintDevice.aidl specifies that there's an upper limit of 2KiB.
+    return KM_ERROR_INVALID_INPUT_LENGTH;
+  }
+
   TPM2B_SENSITIVE_DATA in_data;
   while (size > MAX_STIR_RANDOM_BUFFER_SIZE) {
     memcpy(in_data.buffer, buffer, MAX_STIR_RANDOM_BUFFER_SIZE);
diff --git a/host/commands/secure_env/tpm_remote_provisioning_context.cpp b/host/commands/secure_env/tpm_remote_provisioning_context.cpp
new file mode 100644
index 0000000..38bbdc0
--- /dev/null
+++ b/host/commands/secure_env/tpm_remote_provisioning_context.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright 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.
+ */
+
+#include <algorithm>
+#include <cassert>
+#include <optional>
+
+#include <android-base/logging.h>
+#include <keymaster/cppcose/cppcose.h>
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/hkdf.h>
+#include <openssl/rand.h>
+
+#include "host/commands/secure_env/primary_key_builder.h"
+#include "host/commands/secure_env/tpm_hmac.h"
+#include "tpm_remote_provisioning_context.h"
+
+using namespace cppcose;
+
+TpmRemoteProvisioningContext::TpmRemoteProvisioningContext(
+    TpmResourceManager& resource_manager)
+    : resource_manager_(resource_manager) {
+  std::tie(devicePrivKey_, bcc_) = GenerateBcc(/*testMode=*/false);
+}
+
+std::vector<uint8_t> TpmRemoteProvisioningContext::DeriveBytesFromHbk(
+    const std::string& context, size_t num_bytes) const {
+  // TODO(182928606) Derive using TPM-held secret.
+  std::vector<uint8_t> fakeHbk(32);
+  std::vector<uint8_t> result(num_bytes);
+  if (!HKDF(result.data(), num_bytes,              //
+            EVP_sha256(),                          //
+            fakeHbk.data(), fakeHbk.size(),        //
+            nullptr /* salt */, 0 /* salt len */,  //
+            reinterpret_cast<const uint8_t*>(context.data()), context.size())) {
+    // Should never fail. Even if it could the API has no way of reporting the
+    // error.
+    LOG(ERROR) << "Error calculating HMAC: " << ERR_peek_last_error();
+  }
+
+  return result;
+}
+
+std::unique_ptr<cppbor::Map> TpmRemoteProvisioningContext::CreateDeviceInfo()
+    const {
+  auto result = std::make_unique<cppbor::Map>();
+  result->add(cppbor::Tstr("brand"), cppbor::Tstr("Google"));
+  result->add(cppbor::Tstr("manufacturer"), cppbor::Tstr("Google"));
+  result->add(cppbor::Tstr("product"),
+              cppbor::Tstr("Cuttlefish Virtual Device"));
+  result->canonicalize();
+  return result;
+}
+
+std::pair<std::vector<uint8_t> /* privKey */, cppbor::Array /* BCC */>
+TpmRemoteProvisioningContext::GenerateBcc(bool testMode) const {
+  std::vector<uint8_t> privKey(ED25519_PRIVATE_KEY_LEN);
+  std::vector<uint8_t> pubKey(ED25519_PUBLIC_KEY_LEN);
+
+  // Length is hard-coded in the BoringCrypto API without a constant
+  std::vector<uint8_t> seed;
+  if (testMode) {
+    seed.resize(32);
+    RAND_bytes(seed.data(), seed.size());
+  } else {
+    seed = DeriveBytesFromHbk("Device Key Seed", 32);
+  }
+  ED25519_keypair_from_seed(pubKey.data(), privKey.data(), seed.data());
+
+  auto coseKey = cppbor::Map()
+                     .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
+                     .add(CoseKey::ALGORITHM, EDDSA)
+                     .add(CoseKey::CURVE, ED25519)
+                     .add(CoseKey::KEY_OPS, VERIFY)
+                     .add(CoseKey::PUBKEY_X, pubKey)
+                     .canonicalize();
+  auto sign1Payload =
+      cppbor::Map()
+          .add(1 /* Issuer */, "Issuer")
+          .add(2 /* Subject */, "Subject")
+          .add(-4670552 /* Subject Pub Key */, coseKey.encode())
+          .add(-4670553 /* Key Usage (little-endian order) */,
+               std::vector<uint8_t>{0x20} /* keyCertSign = 1<<5 */)
+          .canonicalize()
+          .encode();
+  auto coseSign1 = constructCoseSign1(privKey,       /* signing key */
+                                      cppbor::Map(), /* extra protected */
+                                      sign1Payload, {} /* AAD */);
+  assert(coseSign1);
+
+  return {privKey,
+          cppbor::Array().add(std::move(coseKey)).add(coseSign1.moveValue())};
+}
+
+std::optional<cppcose::HmacSha256>
+TpmRemoteProvisioningContext::GenerateHmacSha256(
+    const cppcose::bytevec& input) const {
+  auto signing_key_builder = PrimaryKeyBuilder();
+  signing_key_builder.SigningKey();
+  signing_key_builder.UniqueData("Public Key Authentication Key");
+  auto signing_key = signing_key_builder.CreateKey(resource_manager_);
+  if (!signing_key) {
+    LOG(ERROR) << "Could not make MAC key for authenticating the pubkey";
+    return std::nullopt;
+  }
+
+  auto tpm_digest =
+      TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              input.data(), input.size());
+
+  if (!tpm_digest) {
+    LOG(ERROR) << "Could not calculate hmac";
+    return std::nullopt;
+  }
+
+  cppcose::HmacSha256 hmac;
+  if (tpm_digest->size != hmac.size()) {
+    LOG(ERROR) << "TPM-generated digest was too short. Actual size: "
+               << tpm_digest->size << " expected " << hmac.size() << " bytes";
+    return std::nullopt;
+  }
+
+  std::copy(tpm_digest->buffer, tpm_digest->buffer + tpm_digest->size,
+            hmac.begin());
+  return hmac;
+}
diff --git a/host/commands/secure_env/tpm_remote_provisioning_context.h b/host/commands/secure_env/tpm_remote_provisioning_context.h
new file mode 100644
index 0000000..4f285ab
--- /dev/null
+++ b/host/commands/secure_env/tpm_remote_provisioning_context.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <keymaster/remote_provisioning_context.h>
+
+#include "host/commands/secure_env/tpm_resource_manager.h"
+#include "keymaster/cppcose/cppcose.h"
+
+/**
+ * TPM-backed implementation of the provisioning context.
+ */
+class TpmRemoteProvisioningContext
+    : public keymaster::RemoteProvisioningContext {
+ public:
+  TpmRemoteProvisioningContext(TpmResourceManager& resource_manager);
+  ~TpmRemoteProvisioningContext() override = default;
+  std::vector<uint8_t> DeriveBytesFromHbk(const std::string& context,
+                                          size_t numBytes) const override;
+  std::unique_ptr<cppbor::Map> CreateDeviceInfo() const override;
+  std::pair<std::vector<uint8_t>, cppbor::Array> GenerateBcc(
+      bool testMode) const override;
+  std::optional<cppcose::HmacSha256> GenerateHmacSha256(
+      const cppcose::bytevec& input) const override;
+
+ private:
+  TpmResourceManager& resource_manager_;
+};
diff --git a/host/commands/launch/Android.bp b/host/commands/start/Android.bp
similarity index 95%
rename from host/commands/launch/Android.bp
rename to host/commands/start/Android.bp
index 6adf7a2..6343f14 100644
--- a/host/commands/launch/Android.bp
+++ b/host/commands/start/Android.bp
@@ -18,11 +18,11 @@
 }
 
 cc_binary {
-    name: "launch_cvd",
+    name: "cvd_internal_start",
     srcs: [
         "filesystem_explorer.cc",
         "flag_forwarder.cc",
-        "launch_cvd.cc",
+        "main.cc",
     ],
     shared_libs: [
         "libcuttlefish_fs",
diff --git a/host/commands/launch/filesystem_explorer.cc b/host/commands/start/filesystem_explorer.cc
similarity index 100%
rename from host/commands/launch/filesystem_explorer.cc
rename to host/commands/start/filesystem_explorer.cc
diff --git a/host/commands/launch/filesystem_explorer.h b/host/commands/start/filesystem_explorer.h
similarity index 100%
rename from host/commands/launch/filesystem_explorer.h
rename to host/commands/start/filesystem_explorer.h
diff --git a/host/commands/launch/flag_forwarder.cc b/host/commands/start/flag_forwarder.cc
similarity index 100%
rename from host/commands/launch/flag_forwarder.cc
rename to host/commands/start/flag_forwarder.cc
diff --git a/host/commands/launch/flag_forwarder.h b/host/commands/start/flag_forwarder.h
similarity index 100%
rename from host/commands/launch/flag_forwarder.h
rename to host/commands/start/flag_forwarder.h
diff --git a/host/commands/launch/launch_cvd.cc b/host/commands/start/main.cc
similarity index 98%
rename from host/commands/launch/launch_cvd.cc
rename to host/commands/start/main.cc
index f67ce48..c11ac67 100644
--- a/host/commands/launch/launch_cvd.cc
+++ b/host/commands/start/main.cc
@@ -23,13 +23,12 @@
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/subprocess.h"
-#include "host/commands/launch/filesystem_explorer.h"
+#include "host/commands/start/filesystem_explorer.h"
+#include "host/commands/start/flag_forwarder.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/host_tools_version.h"
 #include "host/libs/config/fetcher_config.h"
 
-#include "flag_forwarder.h"
-
 /**
  * If stdin is a tty, that means a user is invoking launch_cvd on the command
  * line and wants automatic file detection for assemble_cvd.
diff --git a/host/commands/cvd_host_bugreport/Android.bp b/host/commands/status/Android.bp
similarity index 94%
copy from host/commands/cvd_host_bugreport/Android.bp
copy to host/commands/status/Android.bp
index 4f6aeda..548c8cf 100644
--- a/host/commands/cvd_host_bugreport/Android.bp
+++ b/host/commands/status/Android.bp
@@ -18,7 +18,7 @@
 }
 
 cc_binary {
-    name: "cvd_host_bugreport",
+    name: "cvd_internal_status",
     srcs: [
         "main.cc",
     ],
@@ -27,7 +27,6 @@
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libjsoncpp",
-        "libziparchive",
     ],
     static_libs: [
         "libcuttlefish_host_config",
diff --git a/host/commands/status/main.cc b/host/commands/status/main.cc
new file mode 100644
index 0000000..1ae5949
--- /dev/null
+++ b/host/commands/status/main.cc
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 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 <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <fstream>
+#include <iomanip>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/environment.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/tee_logging.h"
+#include "host/commands/run_cvd/runner_defs.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/vm_manager/vm_manager.h"
+
+namespace cuttlefish {
+
+int CvdStatusMain(int argc, char** argv) {
+  ::android::base::InitLogging(argv, android::base::StderrLogger);
+  ::android::base::SetLogger(LogToStderrAndFiles({}));
+
+  std::vector<Flag> flags;
+
+  std::int32_t wait_for_launcher;
+  flags.emplace_back(
+      GflagsCompatFlag("wait_for_launcher", wait_for_launcher)
+          .Help("How many seconds to wait for the launcher to respond to the "
+                "status command. A value of zero means wait indefinitely"));
+
+  flags.emplace_back(HelpFlag(flags));
+  flags.emplace_back(UnexpectedArgumentGuard());
+
+  std::vector<std::string> args =
+      ArgsToVec(argc - 1, argv + 1);  // Skip argv[0]
+  CHECK(ParseFlags(flags, args)) << "Could not process command line flags.";
+
+  auto config = CuttlefishConfig::Get();
+  CHECK(config) << "Failed to obtain config object";
+
+  auto instance = config->ForDefaultInstance();
+  auto monitor_path = instance.launcher_monitor_socket_path();
+  CHECK(!monitor_path.empty()) << "No path to launcher monitor found";
+
+  auto monitor_socket = SharedFD::SocketLocalClient(
+      monitor_path.c_str(), false, SOCK_STREAM, wait_for_launcher);
+  CHECK(monitor_socket->IsOpen())
+      << "Unable to connect to launcher monitor at " << monitor_path << ": "
+      << monitor_socket->StrError();
+
+  auto request = LauncherAction::kStatus;
+  auto bytes_sent = monitor_socket->Send(&request, sizeof(request), 0);
+  CHECK(bytes_sent > 0) << "Error sending launcher monitor the status command: "
+                        << monitor_socket->StrError();
+
+  // Perform a select with a timeout to guard against launcher hanging
+  SharedFDSet read_set;
+  read_set.Set(monitor_socket);
+  struct timeval timeout = {wait_for_launcher, 0};
+  int selected = Select(&read_set, nullptr, nullptr,
+                        wait_for_launcher <= 0 ? nullptr : &timeout);
+  CHECK(selected >= 0) << "Failed communication with the launcher monitor: "
+                       << strerror(errno);
+  CHECK(selected > 0)
+      << "Timeout expired waiting for launcher monitor to respond";
+
+  LauncherResponse response;
+  auto bytes_recv = monitor_socket->Recv(&response, sizeof(response), 0);
+  CHECK(bytes_recv > 0) << "Error receiving response from launcher monitor: "
+                        << monitor_socket->StrError();
+  CHECK(response == LauncherResponse::kSuccess)
+      << "Received '" << static_cast<char>(response)
+      << "' response from launcher monitor";
+
+  LOG(INFO) << "run_cvd is active.";
+  return 0;
+}
+
+}  // namespace cuttlefish
+
+int main(int argc, char** argv) {
+  return cuttlefish::CvdStatusMain(argc, argv);
+}
diff --git a/host/commands/stop_cvd/Android.bp b/host/commands/stop/Android.bp
similarity index 96%
rename from host/commands/stop_cvd/Android.bp
rename to host/commands/stop/Android.bp
index a670a25..306a711 100644
--- a/host/commands/stop_cvd/Android.bp
+++ b/host/commands/stop/Android.bp
@@ -18,7 +18,7 @@
 }
 
 cc_binary {
-    name: "stop_cvd",
+    name: "cvd_internal_stop",
     srcs: [
         "main.cc",
     ],
diff --git a/host/commands/stop_cvd/main.cc b/host/commands/stop/main.cc
similarity index 100%
rename from host/commands/stop_cvd/main.cc
rename to host/commands/stop/main.cc
diff --git a/host/commands/tapsetiff/tapsetiff.py b/host/commands/tapsetiff/tapsetiff.py
deleted file mode 100755
index 3e7c9e4..0000000
--- a/host/commands/tapsetiff/tapsetiff.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/python
-
-# 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.
-
-import fcntl
-import struct
-import sys
-
-TUNSETIFF = 0x400454ca
-IFF_TAP = 0x0002
-IFF_NO_PI = 0x1000
-IFF_VNET_HDR = 0x4000
-
-tun_fd = int(sys.argv[1])
-tap_name = sys.argv[2]
-
-ifr = struct.pack('16sH', tap_name, IFF_TAP | IFF_NO_PI | IFF_VNET_HDR)
-fcntl.ioctl(tun_fd, TUNSETIFF, ifr)
diff --git a/host/commands/tombstone_receiver/main.cpp b/host/commands/tombstone_receiver/main.cpp
index 2e0ea69..5fa495d 100644
--- a/host/commands/tombstone_receiver/main.cpp
+++ b/host/commands/tombstone_receiver/main.cpp
@@ -22,23 +22,21 @@
 #include <iomanip>
 #include <sstream>
 
-#include "host/libs/config/logging.h"
 #include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/shared_fd_flag.h"
+#include "host/libs/config/logging.h"
 
-DEFINE_int32(
-    server_fd, -1,
-    "File descriptor to an already created vsock server. If negative a new "
-    "server will be created at the port specified on the config file");
-DEFINE_string(tombstone_dir, "", "directory to write out tombstones in");
+namespace cuttlefish {
 
 static uint num_tombstones_in_last_second = 0;
 static std::string last_tombstone_name = "";
 
-static std::string next_tombstone_path() {
+static std::string next_tombstone_path(const std::string& tombstone_dir) {
   auto in_time_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
   std::stringstream ss;
-  ss << FLAGS_tombstone_dir << "/tombstone_" <<
-    std::put_time(std::gmtime(&in_time_t), "%Y-%m-%d-%H%M%S");
+  ss << tombstone_dir << "/tombstone_"
+     << std::put_time(std::gmtime(&in_time_t), "%Y-%m-%d-%H%M%S");
   auto retval = ss.str();
 
   // Gives tombstones unique names
@@ -54,23 +52,38 @@
   return retval;
 }
 
-#define CHUNK_RECV_MAX_LEN (1024)
-int main(int argc, char** argv) {
-  cuttlefish::DefaultSubprocessLogging(argv);
-  google::ParseCommandLineFlags(&argc, &argv, true);
+static constexpr size_t CHUNK_RECV_MAX_LEN = 1024;
 
-  cuttlefish::SharedFD server_fd = cuttlefish::SharedFD::Dup(FLAGS_server_fd);
-  close(FLAGS_server_fd);
+int TombstoneReceiverMain(int argc, char** argv) {
+  DefaultSubprocessLogging(argv);
 
-  CHECK(server_fd->IsOpen()) << "Error inheriting tombstone server: "
-                             << server_fd->StrError();
+  std::vector<Flag> flags;
+
+  std::string tombstone_dir;
+  flags.emplace_back(GflagsCompatFlag("tombstone_dir", tombstone_dir)
+                         .Help("directory to write out tombstones in"));
+
+  SharedFD server_fd;
+  flags.emplace_back(
+      SharedFDFlag("server_fd", server_fd)
+          .Help("File descriptor to an already created vsock server"));
+
+  flags.emplace_back(HelpFlag(flags));
+  flags.emplace_back(UnexpectedArgumentGuard());
+
+  std::vector<std::string> args =
+      ArgsToVec(argc - 1, argv + 1);  // Skip argv[0]
+  CHECK(ParseFlags(flags, args)) << "Could not process command line flags.";
+
+  CHECK(server_fd->IsOpen()) << "Did not receive a server fd";
+
   LOG(DEBUG) << "Host is starting server on port "
              << server_fd->VsockServerPort();
 
   // Server loop
   while (true) {
-    auto conn = cuttlefish::SharedFD::Accept(*server_fd);
-    std::ofstream file(next_tombstone_path(),
+    auto conn = SharedFD::Accept(*server_fd);
+    std::ofstream file(next_tombstone_path(tombstone_dir),
                        std::ofstream::out | std::ofstream::binary);
 
     while (file.is_open()) {
@@ -87,3 +100,9 @@
 
   return 0;
 }
+
+}  // namespace cuttlefish
+
+int main(int argc, char** argv) {
+  return cuttlefish::TombstoneReceiverMain(argc, argv);
+}
diff --git a/host/frontend/vnc_server/Android.bp b/host/frontend/vnc_server/Android.bp
index c098e34..468590b 100644
--- a/host/frontend/vnc_server/Android.bp
+++ b/host/frontend/vnc_server/Android.bp
@@ -36,15 +36,24 @@
         "libjsoncpp",
         "liblog",
     ],
+    header_libs: [
+        "libcuttlefish_confui_host_headers",
+    ],
     static_libs: [
         "libcuttlefish_host_config",
         "libcuttlefish_screen_connector",
         "libcuttlefish_wayland_server",
+        "libcuttlefish_confui",
+        "libcuttlefish_confui_host",
+        "libft2.nodep",
+        "libteeui",
+        "libteeui_localization",
         "libffi",
         "libjpeg",
         "libgflags",
-        "libwayland_server",
+        "libwayland_crosvm_gpu_display_extension_server_protocols",
         "libwayland_extension_server_protocols",
+        "libwayland_server",
     ],
     defaults: ["cuttlefish_host"],
 }
diff --git a/host/frontend/vnc_server/frame_buffer_watcher.cpp b/host/frontend/vnc_server/frame_buffer_watcher.cpp
index 697a6d9..482f173 100644
--- a/host/frontend/vnc_server/frame_buffer_watcher.cpp
+++ b/host/frontend/vnc_server/frame_buffer_watcher.cpp
@@ -27,12 +27,12 @@
 
 #include <android-base/logging.h>
 #include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/screen_connector/screen_connector.h"
 
 using cuttlefish::vnc::FrameBufferWatcher;
 
-FrameBufferWatcher::FrameBufferWatcher(BlackBoard* bb)
-    : bb_{bb}, hwcomposer{bb_} {
+FrameBufferWatcher::FrameBufferWatcher(BlackBoard* bb,
+                                       ScreenConnector& screen_connector)
+    : bb_{bb}, hwcomposer{screen_connector} {
   for (auto& stripes_vec : stripes_) {
     std::generate_n(std::back_inserter(stripes_vec),
                     SimulatedHWComposer::NumberOfStripes(),
diff --git a/host/frontend/vnc_server/frame_buffer_watcher.h b/host/frontend/vnc_server/frame_buffer_watcher.h
index cbace19..9b559b6 100644
--- a/host/frontend/vnc_server/frame_buffer_watcher.h
+++ b/host/frontend/vnc_server/frame_buffer_watcher.h
@@ -26,12 +26,14 @@
 #include "host/frontend/vnc_server/blackboard.h"
 #include "host/frontend/vnc_server/jpeg_compressor.h"
 #include "host/frontend/vnc_server/simulated_hw_composer.h"
+#include "host/libs/screen_connector/screen_connector.h"
 
 namespace cuttlefish {
 namespace vnc {
 class FrameBufferWatcher {
  public:
-  explicit FrameBufferWatcher(BlackBoard* bb);
+  explicit FrameBufferWatcher(BlackBoard* bb,
+                              ScreenConnector& screen_connector);
   FrameBufferWatcher(const FrameBufferWatcher&) = delete;
   FrameBufferWatcher& operator=(const FrameBufferWatcher&) = delete;
   ~FrameBufferWatcher();
@@ -72,7 +74,7 @@
   mutable std::mutex m_;
   bool closed_ GUARDED_BY(m_){};
   BlackBoard* bb_{};
-  SimulatedHWComposer hwcomposer{bb_};
+  SimulatedHWComposer hwcomposer;
 };
 
 }  // namespace vnc
diff --git a/host/frontend/vnc_server/main.cpp b/host/frontend/vnc_server/main.cpp
index 58c1da6..e036b40 100644
--- a/host/frontend/vnc_server/main.cpp
+++ b/host/frontend/vnc_server/main.cpp
@@ -15,21 +15,40 @@
  */
 
 #include <algorithm>
+#include <memory>
 #include <string>
 
 #include <gflags/gflags.h>
 
+#include "host/frontend/vnc_server/simulated_hw_composer.h"
 #include "host/frontend/vnc_server/vnc_server.h"
 #include "host/frontend/vnc_server/vnc_utils.h"
 #include "host/libs/config/logging.h"
+#include "host/libs/confui/host_mode_ctrl.h"
+#include "host/libs/confui/host_server.h"
 
 DEFINE_bool(agressive, false, "Whether to use agressive server");
+DEFINE_int32(frame_server_fd, -1, "");
 DEFINE_int32(port, 6444, "Port where to listen for connections");
 
 int main(int argc, char* argv[]) {
   cuttlefish::DefaultSubprocessLogging(argv);
   google::ParseCommandLineFlags(&argc, &argv, true);
 
-  cuttlefish::vnc::VncServer vnc_server(FLAGS_port, FLAGS_agressive);
+  auto& host_mode_ctrl = cuttlefish::HostModeCtrl::Get();
+  auto screen_connector_ptr = cuttlefish::vnc::ScreenConnector::Get(
+      FLAGS_frame_server_fd, host_mode_ctrl);
+  auto& screen_connector = *(screen_connector_ptr.get());
+
+  // create confirmation UI service, giving host_mode_ctrl and
+  // screen_connector
+  // keep this singleton object alive until the webRTC process ends
+  static auto& host_confui_server =
+      cuttlefish::confui::HostServer::Get(host_mode_ctrl, screen_connector);
+
+  host_confui_server.Start();
+  // lint does not like the spelling of "agressive", so needs NOTYPO
+  cuttlefish::vnc::VncServer vnc_server(FLAGS_port, FLAGS_agressive,  // NOTYPO
+                                        screen_connector, host_confui_server);
   vnc_server.MainLoop();
 }
diff --git a/host/frontend/vnc_server/simulated_hw_composer.cpp b/host/frontend/vnc_server/simulated_hw_composer.cpp
index ec1df09..bd2223e 100644
--- a/host/frontend/vnc_server/simulated_hw_composer.cpp
+++ b/host/frontend/vnc_server/simulated_hw_composer.cpp
@@ -16,25 +16,21 @@
 
 #include "host/frontend/vnc_server/simulated_hw_composer.h"
 
-#include <gflags/gflags.h>
-
 #include "host/frontend/vnc_server/vnc_utils.h"
 #include "host/libs/config/cuttlefish_config.h"
 
-DEFINE_int32(frame_server_fd, -1, "");
-
 using cuttlefish::vnc::SimulatedHWComposer;
+using ScreenConnector = cuttlefish::vnc::ScreenConnector;
 
-SimulatedHWComposer::SimulatedHWComposer(BlackBoard* bb)
+SimulatedHWComposer::SimulatedHWComposer(ScreenConnector& screen_connector)
     :
 #ifdef FUZZ_TEST_VNC
       engine_{std::random_device{}()},
 #endif
-      bb_{bb},
       stripes_(kMaxQueueElements, &SimulatedHWComposer::EraseHalfOfElements),
-      screen_connector_(ScreenConnector::Get(FLAGS_frame_server_fd)) {
+      screen_connector_(screen_connector) {
   stripe_maker_ = std::thread(&SimulatedHWComposer::MakeStripes, this);
-  screen_connector_->SetCallback(std::move(GetScreenConnectorCallback()));
+  screen_connector_.SetCallback(std::move(GetScreenConnectorCallback()));
 }
 
 SimulatedHWComposer::~SimulatedHWComposer() {
@@ -75,40 +71,40 @@
 
 SimulatedHWComposer::GenerateProcessedFrameCallback
 SimulatedHWComposer::GetScreenConnectorCallback() {
-  return [](std::uint32_t display_number, std::uint8_t* frame_pixels,
+  return [](std::uint32_t display_number, std::uint32_t frame_w,
+            std::uint32_t frame_h, std::uint32_t frame_stride_bytes,
+            std::uint8_t* frame_bytes,
             cuttlefish::vnc::VncScProcessedFrame& processed_frame) {
+    processed_frame.display_number_ = display_number;
     // TODO(171305898): handle multiple displays.
     if (display_number != 0) {
-      processed_frame.is_success_ = false;
-      return;
+      // BUG 186580833: display_number comes from surface_id in crosvm
+      // create_surface from virtio_gpu.rs set_scanout.  We cannot use it as
+      // the display number. Either crosvm virtio-gpu is incorrectly ignoring
+      // scanout id and instead using a monotonically increasing surface id
+      // number as the scanout resource is replaced over time, or frontend code
+      // here is incorrectly assuming  surface id == display id.
+      display_number = 0;
     }
-    const std::uint32_t display_w =
-        SimulatedHWComposer::ScreenConnector::ScreenWidth(display_number);
-    const std::uint32_t display_h =
-        SimulatedHWComposer::ScreenConnector::ScreenHeight(display_number);
-    const std::uint32_t display_stride_bytes =
-        SimulatedHWComposer::ScreenConnector::ScreenStrideBytes(display_number);
-    const std::uint32_t display_bpp =
-        SimulatedHWComposer::ScreenConnector::BytesPerPixel();
-    const std::uint32_t display_size_bytes =
-        SimulatedHWComposer::ScreenConnector::ScreenSizeInBytes(display_number);
+
+    const std::uint32_t frame_bpp = 4;
+    const std::uint32_t frame_size_bytes = frame_h * frame_stride_bytes;
 
     auto& raw_screen = processed_frame.raw_screen_;
-    raw_screen.assign(frame_pixels, frame_pixels + display_size_bytes);
+    raw_screen.assign(frame_bytes, frame_bytes + frame_size_bytes);
 
     static std::uint32_t next_frame_number = 0;
 
     const auto num_stripes = SimulatedHWComposer::kNumStripes;
     for (int i = 0; i < num_stripes; ++i) {
-      std::uint16_t y = (display_h / num_stripes) * i;
+      std::uint16_t y = (frame_h / num_stripes) * i;
 
       // Last frames on the right and/or bottom handle extra pixels
       // when a screen dimension is not evenly divisible by Frame::kNumSlots.
-      std::uint16_t height =
-          display_h / num_stripes +
-          (i + 1 == num_stripes ? display_h % num_stripes : 0);
-      const auto* raw_start = &raw_screen[y * display_w * display_bpp];
-      const auto* raw_end = raw_start + (height * display_w * display_bpp);
+      std::uint16_t height = frame_h / num_stripes +
+                             (i + 1 == num_stripes ? frame_h % num_stripes : 0);
+      const auto* raw_start = &raw_screen[y * frame_w * frame_bpp];
+      const auto* raw_end = raw_start + (height * frame_w * frame_bpp);
       // creating a named object and setting individual data members in order
       // to make klp happy
       // TODO (haining) construct this inside the call when not compiling
@@ -117,8 +113,8 @@
       s.index = i;
       s.x = 0;
       s.y = y;
-      s.width = display_w;
-      s.stride = display_stride_bytes;
+      s.width = frame_w;
+      s.stride = frame_stride_bytes;
       s.height = height;
       s.frame_id = next_frame_number++;
       s.raw_data.assign(raw_start, raw_end);
@@ -137,12 +133,11 @@
    * callback should be set before the first WaitForAtLeastOneClientConnection()
    * (b/178504150) and the first OnFrameAfter().
    */
-  if (!screen_connector_->IsCallbackSet()) {
+  if (!screen_connector_.IsCallbackSet()) {
     LOG(FATAL) << "ScreenConnector callback hasn't been set before MakeStripes";
   }
   while (!closed()) {
-    bb_->WaitForAtLeastOneClientConnection();
-    auto sim_hw_processed_frame = screen_connector_->OnNextFrame();
+    auto sim_hw_processed_frame = screen_connector_.OnNextFrame();
     // sim_hw_processed_frame has display number from the guest
     if (!sim_hw_processed_frame.is_success_) {
       continue;
@@ -174,5 +169,5 @@
 int SimulatedHWComposer::NumberOfStripes() { return kNumStripes; }
 
 void SimulatedHWComposer::ReportClientsConnected() {
-  screen_connector_->ReportClientsConnected(true);
+  screen_connector_.ReportClientsConnected(true);
 }
diff --git a/host/frontend/vnc_server/simulated_hw_composer.h b/host/frontend/vnc_server/simulated_hw_composer.h
index fadb4a1..ff7e9dd 100644
--- a/host/frontend/vnc_server/simulated_hw_composer.h
+++ b/host/frontend/vnc_server/simulated_hw_composer.h
@@ -26,33 +26,17 @@
 
 #include "common/libs/concurrency/thread_annotations.h"
 #include "common/libs/concurrency/thread_safe_queue.h"
-#include "host/frontend/vnc_server/blackboard.h"
 #include "host/frontend/vnc_server/vnc_utils.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/screen_connector/screen_connector.h"
 
 namespace cuttlefish {
 namespace vnc {
-/**
- * ScreenConnectorImpl will generate this, and enqueue
- *
- * It's basically a (processed) frame, so it:
- *   must be efficiently std::move-able
- * Also, for the sake of algorithm simplicity:
- *   must be default-constructable & assignable
- *
- */
-struct VncScProcessedFrame : public ScreenConnectorFrameInfo {
-  Message raw_screen_;
-  std::deque<Stripe> stripes_;
-};
-
 class SimulatedHWComposer {
  public:
-  using ScreenConnector = ScreenConnector<VncScProcessedFrame>;
   using GenerateProcessedFrameCallback = ScreenConnector::GenerateProcessedFrameCallback;
 
-  SimulatedHWComposer(BlackBoard* bb);
+  SimulatedHWComposer(ScreenConnector& screen_connector);
   SimulatedHWComposer(const SimulatedHWComposer&) = delete;
   SimulatedHWComposer& operator=(const SimulatedHWComposer&) = delete;
   ~SimulatedHWComposer();
@@ -80,10 +64,9 @@
   constexpr static std::size_t kMaxQueueElements = 64;
   bool closed_ GUARDED_BY(m_){};
   std::mutex m_;
-  BlackBoard* bb_{};
   ThreadSafeQueue<Stripe> stripes_;
   std::thread stripe_maker_;
-  std::unique_ptr<ScreenConnector> screen_connector_;
+  ScreenConnector& screen_connector_;
 };
 }  // namespace vnc
 }  // namespace cuttlefish
diff --git a/host/frontend/vnc_server/virtual_inputs.cpp b/host/frontend/vnc_server/virtual_inputs.cpp
index 3143a6d..4876338 100644
--- a/host/frontend/vnc_server/virtual_inputs.cpp
+++ b/host/frontend/vnc_server/virtual_inputs.cpp
@@ -15,8 +15,10 @@
  */
 
 #include "host/frontend/vnc_server/virtual_inputs.h"
-#include <gflags/gflags.h>
+
 #include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <gflags/gflags.h>
 #include <linux/input.h>
 #include <linux/uinput.h>
 
@@ -25,13 +27,15 @@
 #include <thread>
 #include "keysyms.h"
 
-#include <common/libs/fs/shared_select.h>
-#include <host/libs/config/cuttlefish_config.h>
+#include "common/libs/confui/confui.h"
+#include "common/libs/fs/shared_select.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/logging.h"
 
 using cuttlefish::vnc::VirtualInputs;
 
-DEFINE_int32(touch_fd, -1,
-             "A fd for a socket where to accept touch connections");
+DEFINE_string(touch_fds, "",
+              "A list of fds for sockets where to accept touch connections");
 
 DEFINE_int32(keyboard_fd, -1,
              "A fd for a socket where to accept keyboard connections");
@@ -312,9 +316,10 @@
   }
 
   void ClientConnectorLoop() {
-    auto touch_server = cuttlefish::SharedFD::Dup(FLAGS_touch_fd);
-    close(FLAGS_touch_fd);
-    FLAGS_touch_fd = -1;
+    auto touch_fd =
+        std::stoi(android::base::Split(FLAGS_touch_fds, ",").front());
+    auto touch_server = cuttlefish::SharedFD::Dup(touch_fd);
+    close(touch_fd);
 
     auto keyboard_server = cuttlefish::SharedFD::Dup(FLAGS_keyboard_fd);
     close(FLAGS_keyboard_fd);
@@ -347,6 +352,61 @@
 
 VirtualInputs::VirtualInputs() { AddKeyMappings(&keymapping_); }
 
-VirtualInputs* VirtualInputs::Get() {
-  return new SocketVirtualInputs();
+/**
+ * Depending on the host mode (e.g. android, confirmation ui(tee), etc)
+ * deliver the inputs to the right input implementation
+ * e.g. ConfUI's input or regular socket based input
+ */
+class VirtualInputDemux : public VirtualInputs {
+ public:
+  VirtualInputDemux(cuttlefish::confui::HostVirtualInput& confui_input)
+      : confui_input_{confui_input} {}
+  virtual ~VirtualInputDemux() = default;
+
+  virtual void GenerateKeyPressEvent(int code, bool down) override;
+  virtual void PressPowerButton(bool down) override;
+  virtual void HandlePointerEvent(bool touch_down, int x, int y) override;
+
+ private:
+  SocketVirtualInputs socket_virtual_input_;
+  cuttlefish::confui::HostVirtualInput& confui_input_;
+};
+
+void VirtualInputDemux::GenerateKeyPressEvent(int code, bool down) {
+  // confui input is active only in the confirmation UI
+  // also, socket virtual input should be inactive in the confirmation
+  // UI session
+  if (confui_input_.IsConfUiActive()) {
+    if (code == cuttlefish::xk::Menu) {
+      // release menu button in confirmation UI means for now cancel
+      confui_input_.PressCancelButton(down);
+    }
+    ConfUiLog(DEBUG) << "the key" << code << "ignored."
+                     << "currently confirmation UI handles"
+                     << "menu and power only.";
+    return;
+  }
+  socket_virtual_input_.GenerateKeyPressEvent(code, down);
+}
+
+void VirtualInputDemux::PressPowerButton(bool down) {
+  if (confui_input_.IsConfUiActive()) {
+    confui_input_.PressConfirmButton(down);
+    return;
+  }
+  socket_virtual_input_.PressPowerButton(down);
+}
+
+void VirtualInputDemux::HandlePointerEvent(bool touch_down, int x, int y) {
+  if (confui_input_.IsConfUiActive()) {
+    ConfUiLog(DEBUG) << "currently confirmation UI ignores pointer events at ("
+                     << x << ", " << y << ")";
+    return;
+  }
+  socket_virtual_input_.HandlePointerEvent(touch_down, x, y);
+}
+
+std::shared_ptr<VirtualInputs> VirtualInputs::Get(
+    cuttlefish::confui::HostVirtualInput& confui_input) {
+  return std::make_shared<VirtualInputDemux>(confui_input);
 }
diff --git a/host/frontend/vnc_server/virtual_inputs.h b/host/frontend/vnc_server/virtual_inputs.h
index c22de15..f30202e 100644
--- a/host/frontend/vnc_server/virtual_inputs.h
+++ b/host/frontend/vnc_server/virtual_inputs.h
@@ -16,17 +16,20 @@
  * limitations under the License.
  */
 
-#include "vnc_utils.h"
-
 #include <map>
+#include <memory>
 #include <mutex>
 
+#include "host/libs/confui/host_virtual_input.h"
+#include "vnc_utils.h"
+
 namespace cuttlefish {
 namespace vnc {
 
 class VirtualInputs {
  public:
-  static VirtualInputs* Get();
+  static std::shared_ptr<VirtualInputs> Get(
+      cuttlefish::confui::HostVirtualInput& confui_input);
 
   virtual ~VirtualInputs() = default;
 
diff --git a/host/frontend/vnc_server/vnc_server.cpp b/host/frontend/vnc_server/vnc_server.cpp
index dd489b8..eff0d57 100644
--- a/host/frontend/vnc_server/vnc_server.cpp
+++ b/host/frontend/vnc_server/vnc_server.cpp
@@ -16,6 +16,8 @@
 
 #include "host/frontend/vnc_server/vnc_server.h"
 
+#include <memory>
+
 #include <android-base/logging.h>
 #include "common/libs/utils/tcp_socket.h"
 #include "host/frontend/vnc_server/blackboard.h"
@@ -27,11 +29,13 @@
 
 using cuttlefish::vnc::VncServer;
 
-VncServer::VncServer(int port, bool aggressive)
+VncServer::VncServer(int port, bool aggressive,
+                     cuttlefish::vnc::ScreenConnector& screen_connector,
+                     cuttlefish::confui::HostVirtualInput& confui_input)
     : server_(port),
-    virtual_inputs_(VirtualInputs::Get()),
-    frame_buffer_watcher_{&bb_},
-    aggressive_{aggressive} {}
+      virtual_inputs_(VirtualInputs::Get(confui_input)),
+      frame_buffer_watcher_{&bb_, screen_connector},
+      aggressive_{aggressive} {}
 
 void VncServer::MainLoop() {
   while (true) {
diff --git a/host/frontend/vnc_server/vnc_server.h b/host/frontend/vnc_server/vnc_server.h
index 3ae37d8..2751fd1 100644
--- a/host/frontend/vnc_server/vnc_server.h
+++ b/host/frontend/vnc_server/vnc_server.h
@@ -28,13 +28,18 @@
 #include "host/frontend/vnc_server/virtual_inputs.h"
 #include "host/frontend/vnc_server/vnc_client_connection.h"
 #include "host/frontend/vnc_server/vnc_utils.h"
+#include "host/libs/confui/host_mode_ctrl.h"
+#include "host/libs/confui/host_virtual_input.h"
+#include "host/libs/screen_connector/screen_connector.h"
 
 namespace cuttlefish {
 namespace vnc {
 
 class VncServer {
  public:
-  explicit VncServer(int port, bool aggressive);
+  explicit VncServer(int port, bool aggressive,
+                     ScreenConnector& screen_connector,
+                     cuttlefish::confui::HostVirtualInput& confui_input);
 
   VncServer(const VncServer&) = delete;
   VncServer& operator=(const VncServer&) = delete;
@@ -47,8 +52,10 @@
   void StartClientThread(ClientSocket sock);
 
   ServerSocket server_;
+
   std::shared_ptr<VirtualInputs> virtual_inputs_;
   BlackBoard bb_;
+
   FrameBufferWatcher frame_buffer_watcher_;
   bool aggressive_{};
 };
diff --git a/host/frontend/vnc_server/vnc_utils.h b/host/frontend/vnc_server/vnc_utils.h
index 34df434..7ec19f7 100644
--- a/host/frontend/vnc_server/vnc_utils.h
+++ b/host/frontend/vnc_server/vnc_utils.h
@@ -18,12 +18,14 @@
 
 #include <array>
 #include <cstdint>
+#include <memory>
 #include <utility>
 #include <vector>
 
 #include "common/libs/utils/size_utils.h"
 #include "common/libs/utils/tcp_socket.h"
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/screen_connector/screen_connector.h"
 
 namespace cuttlefish {
 namespace vnc {
@@ -63,5 +65,26 @@
   ScreenOrientation orientation{};
 };
 
+/**
+ * ScreenConnectorImpl will generate this, and enqueue
+ *
+ * It's basically a (processed) frame, so it:
+ *   must be efficiently std::move-able
+ * Also, for the sake of algorithm simplicity:
+ *   must be default-constructable & assignable
+ *
+ */
+struct VncScProcessedFrame : public ScreenConnectorFrameInfo {
+  Message raw_screen_;
+  std::deque<Stripe> stripes_;
+  std::unique_ptr<VncScProcessedFrame> Clone() {
+    VncScProcessedFrame* cloned_frame = new VncScProcessedFrame();
+    cloned_frame->raw_screen_ = raw_screen_;
+    cloned_frame->stripes_ = stripes_;
+    return std::unique_ptr<VncScProcessedFrame>(cloned_frame);
+  }
+};
+using ScreenConnector = cuttlefish::ScreenConnector<VncScProcessedFrame>;
+
 }  // namespace vnc
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc/Android.bp b/host/frontend/webrtc/Android.bp
index e0890ad..42f7fa4 100644
--- a/host/frontend/webrtc/Android.bp
+++ b/host/frontend/webrtc/Android.bp
@@ -22,6 +22,7 @@
     srcs: [
         "lib/audio_device.cpp",
         "lib/audio_track_source_impl.cpp",
+        "lib/camera_streamer.cpp",
         "lib/client_handler.cpp",
         "lib/keyboard.cpp",
         "lib/local_recorder.cpp",
@@ -50,8 +51,9 @@
         "libgflags",
         "libdrm",
         "libffi",
-        "libwayland_server",
+        "libwayland_crosvm_gpu_display_extension_server_protocols",
         "libwayland_extension_server_protocols",
+        "libwayland_server",
         "libwebsockets",
         "libcap",
         "libcuttlefish_utils",
@@ -74,6 +76,7 @@
     srcs: [
         "adb_handler.cpp",
         "audio_handler.cpp",
+        "bluetooth_handler.cpp",
         "connection_observer.cpp",
         "cvd_video_frame_buffer.cpp",
         "display_handler.cpp",
@@ -83,6 +86,7 @@
     header_libs: [
         "webrtc_signaling_headers",
         "libwebrtc_absl_headers",
+        "libcuttlefish_confui_host_headers",
     ],
     static_libs: [
         "libwebrtc_absl_base",
@@ -103,6 +107,11 @@
         "libcuttlefish_screen_connector",
         "libcuttlefish_utils",
         "libcuttlefish_wayland_server",
+        "libcuttlefish_confui",
+        "libcuttlefish_confui_host",
+        "libft2.nodep",
+        "libteeui",
+        "libteeui_localization",
         "libdrm",
         "libevent",
         "libffi",
@@ -110,6 +119,7 @@
         "libopus",
         "libsrtp2",
         "libvpx",
+        "libwayland_crosvm_gpu_display_extension_server_protocols",
         "libwayland_extension_server_protocols",
         "libwayland_server",
         "libwebrtc",
diff --git a/host/frontend/webrtc/adb_handler.cpp b/host/frontend/webrtc/adb_handler.cpp
index ae2eccf..7ff55fb 100644
--- a/host/frontend/webrtc/adb_handler.cpp
+++ b/host/frontend/webrtc/adb_handler.cpp
@@ -43,7 +43,13 @@
     return SharedFD();
   }
 
-  return SharedFD::SocketLocalClient(port, SOCK_STREAM);
+  auto local_client = SharedFD::SocketLocalClient(port, SOCK_STREAM);
+  if (!local_client->IsOpen()) {
+    LOG(WARNING) << "Failed to connect to ADB server socket (non-Android guest?) Using /dev/null workaround."
+                 << local_client->StrError();
+    return SharedFD::Open("/dev/null", O_RDWR);
+  }
+  return local_client;
 }
 
 }  // namespace
diff --git a/host/frontend/webrtc/audio_handler.cpp b/host/frontend/webrtc/audio_handler.cpp
index 4046995..1cd8938 100644
--- a/host/frontend/webrtc/audio_handler.cpp
+++ b/host/frontend/webrtc/audio_handler.cpp
@@ -25,6 +25,28 @@
 namespace cuttlefish {
 namespace {
 
+const virtio_snd_jack_info JACKS[] = {};
+constexpr uint32_t NUM_JACKS = sizeof(JACKS) / sizeof(JACKS[0]);
+
+const virtio_snd_chmap_info CHMAPS[] = {{
+    .hdr = { .hda_fn_nid = Le32(0), },
+    .direction = (uint8_t) AudioStreamDirection::VIRTIO_SND_D_OUTPUT,
+    .channels = 2,
+    .positions = {
+        (uint8_t) AudioChannelMap::VIRTIO_SND_CHMAP_FL,
+        (uint8_t) AudioChannelMap::VIRTIO_SND_CHMAP_FR
+    },
+}, {
+    .hdr = { .hda_fn_nid = Le32(0), },
+    .direction = (uint8_t) AudioStreamDirection::VIRTIO_SND_D_INPUT,
+    .channels = 2,
+    .positions = {
+        (uint8_t) AudioChannelMap::VIRTIO_SND_CHMAP_FL,
+        (uint8_t) AudioChannelMap::VIRTIO_SND_CHMAP_FR
+    },
+}};
+constexpr uint32_t NUM_CHMAPS = sizeof(CHMAPS) / sizeof(CHMAPS[0]);
+
 const virtio_snd_pcm_info STREAMS[] = {{
     .hdr =
         {
@@ -35,10 +57,10 @@
     // formats: It only takes the bits_per_sample as a parameter and assumes
     // the underlying format to be one of the following:
     .formats = Le64(
-        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_U8) |
-        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_U16) |
-        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_U24) |
-        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_U32)),
+        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_S8) |
+        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_S16) |
+        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_S24) |
+        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_S32)),
     .rates = Le64(
         (((uint64_t)1) << (uint8_t)AudioStreamRate::VIRTIO_SND_PCM_RATE_5512) |
         (((uint64_t)1) << (uint8_t)AudioStreamRate::VIRTIO_SND_PCM_RATE_8000) |
@@ -70,10 +92,10 @@
     // formats: It only takes the bits_per_sample as a parameter and assumes
     // the underlying format to be one of the following:
     .formats = Le64(
-        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_U8) |
-        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_U16) |
-        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_U24) |
-        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_U32)),
+        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_S8) |
+        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_S16) |
+        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_S24) |
+        (((uint64_t)1) << (uint8_t)AudioStreamFormat::VIRTIO_SND_PCM_FMT_S32)),
     .rates = Le64(
         (((uint64_t)1) << (uint8_t)AudioStreamRate::VIRTIO_SND_PCM_RATE_5512) |
         (((uint64_t)1) << (uint8_t)AudioStreamRate::VIRTIO_SND_PCM_RATE_8000) |
@@ -271,7 +293,7 @@
 [[noreturn]] void AudioHandler::Loop() {
   for (;;) {
     auto audio_client = audio_server_->AcceptClient(
-        NUM_STREAMS, 0 /* num_jacks, */, 0 /* num_chmaps, */,
+        NUM_STREAMS, NUM_JACKS, NUM_CHMAPS,
         262144 /* tx_shm_len */, 262144 /* rx_shm_len */);
     CHECK(audio_client) << "Failed to create audio client connection instance";
 
@@ -362,6 +384,28 @@
   cmd.Reply(AudioStatus::VIRTIO_SND_S_OK);
 }
 
+void AudioHandler::ChmapsInfo(ChmapInfoCommand& cmd) {
+  if (cmd.start_id() >= NUM_CHMAPS ||
+      cmd.start_id() + cmd.count() > NUM_CHMAPS) {
+    cmd.Reply(AudioStatus::VIRTIO_SND_S_BAD_MSG, {});
+    return;
+  }
+  std::vector<virtio_snd_chmap_info> chmap_info(
+      &CHMAPS[cmd.start_id()], &CHMAPS[cmd.start_id()] + cmd.count());
+  cmd.Reply(AudioStatus::VIRTIO_SND_S_OK, chmap_info);
+}
+
+void AudioHandler::JacksInfo(JackInfoCommand& cmd) {
+  if (cmd.start_id() >= NUM_JACKS ||
+      cmd.start_id() + cmd.count() > NUM_JACKS) {
+    cmd.Reply(AudioStatus::VIRTIO_SND_S_BAD_MSG, {});
+    return;
+  }
+  std::vector<virtio_snd_jack_info> jack_info(
+      &JACKS[cmd.start_id()], &JACKS[cmd.start_id()] + cmd.count());
+  cmd.Reply(AudioStatus::VIRTIO_SND_S_OK, jack_info);
+}
+
 void AudioHandler::OnPlaybackBuffer(TxBuffer buffer) {
   auto stream_id = buffer.stream_id();
   auto& stream_desc = stream_descs_[stream_id];
diff --git a/host/frontend/webrtc/audio_handler.h b/host/frontend/webrtc/audio_handler.h
index 3802052..4da645c 100644
--- a/host/frontend/webrtc/audio_handler.h
+++ b/host/frontend/webrtc/audio_handler.h
@@ -63,6 +63,8 @@
   void ReleaseStream(StreamControlCommand& cmd) override;
   void StartStream(StreamControlCommand& cmd) override;
   void StopStream(StreamControlCommand& cmd) override;
+  void ChmapsInfo(ChmapInfoCommand& cmd) override;
+  void JacksInfo(JackInfoCommand& cmd) override;
 
   void OnPlaybackBuffer(TxBuffer buffer) override;
   void OnCaptureBuffer(RxBuffer buffer) override;
diff --git a/host/frontend/webrtc/bluetooth_handler.cpp b/host/frontend/webrtc/bluetooth_handler.cpp
new file mode 100644
index 0000000..219c36e
--- /dev/null
+++ b/host/frontend/webrtc/bluetooth_handler.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#include "host/frontend/webrtc/bluetooth_handler.h"
+
+#include <unistd.h>
+
+#include <android-base/logging.h>
+
+using namespace android;
+
+namespace cuttlefish {
+namespace webrtc_streaming {
+
+BluetoothHandler::BluetoothHandler(
+    const int rootCanalTestPort,
+    std::function<void(const uint8_t *, size_t)> send_to_client)
+    : send_to_client_(send_to_client),
+      rootcanal_socket_(
+          SharedFD::SocketLocalClient(rootCanalTestPort, SOCK_STREAM)),
+      shutdown_(SharedFD::Event(0, 0)) {
+  std::thread loop([this]() { ReadLoop(); });
+  read_thread_.swap(loop);
+}
+
+BluetoothHandler::~BluetoothHandler() {
+  // Send a message to the looper to shut down.
+  uint64_t v = 1;
+  shutdown_->Write(&v, sizeof(v));
+  // Shut down the socket as well.  Not strictly necessary.
+  rootcanal_socket_->Shutdown(SHUT_RDWR);
+  read_thread_.join();
+}
+
+void BluetoothHandler::ReadLoop() {
+  while (1) {
+    uint8_t buffer[4096];
+
+    read_set_.Set(shutdown_);
+    read_set_.Set(rootcanal_socket_);
+    Select(&read_set_, nullptr, nullptr, nullptr);
+
+    if (read_set_.IsSet(rootcanal_socket_)) {
+      auto read = rootcanal_socket_->Read(buffer, sizeof(buffer));
+      if (read < 0) {
+        PLOG(ERROR) << "Error on reading from RootCanal socket.";
+        break;
+      }
+      if (read) {
+        send_to_client_(buffer, read);
+      }
+    }
+
+    if (read_set_.IsSet(shutdown_)) {
+      LOG(INFO) << "BluetoothHandler is shutting down.";
+      break;
+    }
+  }
+}
+
+void BluetoothHandler::handleMessage(const uint8_t *msg, size_t len) {
+  size_t sent = 0;
+  while (sent < len) {
+    auto this_sent = rootcanal_socket_->Write(&msg[sent], len - sent);
+    if (this_sent < 0) {
+      PLOG(FATAL) << "Error writing to rootcanal socket.";
+      return;
+    }
+    sent += this_sent;
+  }
+}
+
+}  // namespace webrtc_streaming
+}  // namespace cuttlefish
diff --git a/host/frontend/webrtc/bluetooth_handler.h b/host/frontend/webrtc/bluetooth_handler.h
new file mode 100644
index 0000000..72248cf
--- /dev/null
+++ b/host/frontend/webrtc/bluetooth_handler.h
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <thread>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+
+namespace cuttlefish {
+namespace webrtc_streaming {
+
+struct BluetoothHandler {
+  explicit BluetoothHandler(
+      const int rootCanalTestPort,
+      std::function<void(const uint8_t *, size_t)> send_to_client);
+
+  ~BluetoothHandler();
+
+  void handleMessage(const uint8_t *msg, size_t len);
+
+ private:
+  std::function<void(const uint8_t *, size_t)> send_to_client_;
+
+  void ReadLoop();
+
+  SharedFD rootcanal_socket_;
+  SharedFD shutdown_;
+  SharedFDSet read_set_;
+  std::thread read_thread_;
+};
+
+}  // namespace webrtc_streaming
+}  // namespace cuttlefish
diff --git a/host/frontend/webrtc/connection_observer.cpp b/host/frontend/webrtc/connection_observer.cpp
index 3be8fa4..21c1312 100644
--- a/host/frontend/webrtc/connection_observer.cpp
+++ b/host/frontend/webrtc/connection_observer.cpp
@@ -20,6 +20,7 @@
 
 #include <linux/input.h>
 
+#include <chrono>
 #include <map>
 #include <set>
 #include <thread>
@@ -30,9 +31,11 @@
 #include <android-base/logging.h>
 #include <gflags/gflags.h>
 
+#include "common/libs/confui/confui.h"
 #include "common/libs/fs/shared_buf.h"
 #include "host/frontend/webrtc/adb_handler.h"
-#include "host/frontend/webrtc/kernel_log_events_handler.h"
+#include "host/frontend/webrtc/bluetooth_handler.h"
+#include "host/frontend/webrtc/lib/camera_controller.h"
 #include "host/frontend/webrtc/lib/utils.h"
 #include "host/libs/config/cuttlefish_config.h"
 
@@ -88,23 +91,36 @@
   }
 }
 
+/**
+ * connection observer implementation for regular android mode.
+ * i.e. when it is not in the confirmation UI mode (or TEE),
+ * the control flow will fall back to this ConnectionObserverForAndroid
+ */
 class ConnectionObserverImpl
     : public cuttlefish::webrtc_streaming::ConnectionObserver {
  public:
-  ConnectionObserverImpl(cuttlefish::InputSockets& input_sockets,
-                         cuttlefish::SharedFD kernel_log_events_fd,
-                         std::map<std::string, cuttlefish::SharedFD>
-                             commands_to_custom_action_servers,
-                         std::weak_ptr<DisplayHandler> display_handler)
+  ConnectionObserverImpl(
+      cuttlefish::InputSockets &input_sockets,
+      cuttlefish::KernelLogEventsHandler *kernel_log_events_handler,
+      std::map<std::string, cuttlefish::SharedFD>
+          commands_to_custom_action_servers,
+      std::weak_ptr<DisplayHandler> display_handler,
+      CameraController *camera_controller,
+      cuttlefish::confui::HostVirtualInput &confui_input)
       : input_sockets_(input_sockets),
-        kernel_log_events_client_(kernel_log_events_fd),
+        kernel_log_events_handler_(kernel_log_events_handler),
         commands_to_custom_action_servers_(commands_to_custom_action_servers),
-        weak_display_handler_(display_handler) {}
+        weak_display_handler_(display_handler),
+        camera_controller_(camera_controller),
+        confui_input_(confui_input) {}
   virtual ~ConnectionObserverImpl() {
     auto display_handler = weak_display_handler_.lock();
     if (display_handler) {
       display_handler->DecClientCount();
     }
+    if (kernel_log_subscription_id_ != -1) {
+      kernel_log_events_handler_->Unsubscribe(kernel_log_subscription_id_);
+    }
   }
 
   void OnConnected(std::function<void(const uint8_t *, size_t, bool)>
@@ -112,16 +128,30 @@
     auto display_handler = weak_display_handler_.lock();
     if (display_handler) {
       display_handler->IncClientCount();
-      // A long time may pass before the next frame comes up from the guest.
-      // Send the last one to avoid showing a black screen to the user during
-      // that time.
-      display_handler->SendLastFrame();
+      std::thread th([this]() {
+        // The encoder in libwebrtc won't drop 5 consecutive frames due to frame
+        // size, so we make sure at least 5 frames are sent every time a client
+        // connects to ensure they receive at least one.
+        constexpr int kNumFrames = 5;
+        constexpr int kMillisPerFrame = 16;
+        for (int i = 0; i < kNumFrames; ++i) {
+          auto display_handler = weak_display_handler_.lock();
+          display_handler->SendLastFrame();
+          if (i < kNumFrames - 1) {
+            std::this_thread::sleep_for(std::chrono::milliseconds(kMillisPerFrame));
+          }
+        }
+      });
+      th.detach();
     }
   }
 
-  void OnTouchEvent(const std::string & /*display_label*/, int x, int y,
+  void OnTouchEvent(const std::string &display_label, int x, int y,
                     bool down) override {
-
+    if (confui_input_.IsConfUiActive()) {
+      ConfUiLog(DEBUG) << "touch event ignored in confirmation UI mode";
+      return;
+    }
     auto buffer = GetEventBuffer();
     if (!buffer) {
       LOG(ERROR) << "Failed to allocate event buffer";
@@ -131,22 +161,25 @@
     buffer->AddEvent(EV_ABS, ABS_Y, y);
     buffer->AddEvent(EV_KEY, BTN_TOUCH, down);
     buffer->AddEvent(EV_SYN, SYN_REPORT, 0);
-    cuttlefish::WriteAll(input_sockets_.touch_client,
+    cuttlefish::WriteAll(input_sockets_.GetTouchClientByLabel(display_label),
                          reinterpret_cast<const char *>(buffer->data()),
                          buffer->size());
   }
 
-  void OnMultiTouchEvent(const std::string & /*display_label*/, Json::Value id,
+  void OnMultiTouchEvent(const std::string &display_label, Json::Value id,
                          Json::Value slot, Json::Value x, Json::Value y,
-                         bool down, int size) override {
-
+                         bool down, int size) {
+    if (confui_input_.IsConfUiActive()) {
+      ConfUiLog(DEBUG) << "multi-touch event ignored in confirmation UI mode";
+      return;
+    }
     auto buffer = GetEventBuffer();
     if (!buffer) {
       LOG(ERROR) << "Failed to allocate event buffer";
       return;
     }
 
-    for (int i=0; i<size; i++) {
+    for (int i = 0; i < size; i++) {
       auto this_slot = slot[i].asInt();
       auto this_id = id[i].asInt();
       auto this_x = x[i].asInt();
@@ -176,12 +209,30 @@
     }
 
     buffer->AddEvent(EV_SYN, SYN_REPORT, 0);
-    cuttlefish::WriteAll(input_sockets_.touch_client,
+    cuttlefish::WriteAll(input_sockets_.GetTouchClientByLabel(display_label),
                          reinterpret_cast<const char *>(buffer->data()),
                          buffer->size());
   }
 
   void OnKeyboardEvent(uint16_t code, bool down) override {
+    if (confui_input_.IsConfUiActive()) {
+      ConfUiLog(DEBUG) << "and it's confirmation UI mode";
+      switch (code) {
+        case KEY_POWER:
+          ConfUiLog(DEBUG) << "Power pushed mode";
+          confui_input_.PressConfirmButton(down);
+          break;
+        case KEY_MENU:
+          confui_input_.PressCancelButton(down);
+          break;
+        default:
+          ConfUiLog(DEBUG) << "key" << code
+                           << "is ignored in confirmation UI mode";
+          break;
+      }
+      return;
+    }
+
     auto buffer = GetEventBuffer();
     if (!buffer) {
       LOG(ERROR) << "Failed to allocate event buffer";
@@ -222,9 +273,11 @@
   void OnControlChannelOpen(std::function<bool(const Json::Value)>
                             control_message_sender) override {
     LOG(VERBOSE) << "Control Channel open";
-    kernel_log_events_handler_.reset(new cuttlefish::webrtc_streaming::KernelLogEventsHandler(
-        kernel_log_events_client_,
-        control_message_sender));
+    if (camera_controller_) {
+      camera_controller_->SetMessageSender(control_message_sender);
+    }
+    kernel_log_subscription_id_ =
+        kernel_log_events_handler_->AddSubscriber(control_message_sender);
   }
   void OnControlMessage(const uint8_t* msg, size_t size) override {
     Json::Value evt;
@@ -236,68 +289,118 @@
       LOG(ERROR) << "Received invalid JSON object over control channel: " << errorMessage;
       return;
     }
-    auto result =
-        webrtc_streaming::ValidationResult::ValidateJsonObject(evt, "command",
-                           {{"command", Json::ValueType::stringValue},
-                            {"state", Json::ValueType::stringValue}});
+
+    auto result = webrtc_streaming::ValidationResult::ValidateJsonObject(
+        evt, "command",
+        /*required_fields=*/{{"command", Json::ValueType::stringValue}},
+        /*optional_fields=*/
+        {
+            {"button_state", Json::ValueType::stringValue},
+            {"lid_switch_open", Json::ValueType::booleanValue},
+            {"hinge_angle_value", Json::ValueType::intValue},
+        });
     if (!result.ok()) {
       LOG(ERROR) << result.error();
       return;
     }
     auto command = evt["command"].asString();
-    auto state = evt["state"].asString();
 
-    LOG(VERBOSE) << "Control command: " << command << " (" << state << ")";
+    if (command == "device_state") {
+      if (evt.isMember("lid_switch_open")) {
+        // InputManagerService treats a value of 0 as open and 1 as closed, so
+        // invert the lid_switch_open value that is sent to the input device.
+        OnSwitchEvent(SW_LID, !evt["lid_switch_open"].asBool());
+      }
+      if (evt.isMember("hinge_angle_value")) {
+        // TODO(b/181157794) Propagate hinge angle sensor data using a custom
+        // Sensor HAL.
+      }
+      return;
+    } else if (command.rfind("camera_", 0) == 0 && camera_controller_) {
+      // Handle commands starting with "camera_" by camera controller
+      camera_controller_->HandleMessage(evt);
+      return;
+    }
+
+    auto button_state = evt["button_state"].asString();
+    LOG(VERBOSE) << "Control command: " << command << " (" << button_state
+                 << ")";
     if (command == "power") {
-      OnKeyboardEvent(KEY_POWER, state == "down");
+      OnKeyboardEvent(KEY_POWER, button_state == "down");
     } else if (command == "home") {
-      OnKeyboardEvent(KEY_HOMEPAGE, state == "down");
+      OnKeyboardEvent(KEY_HOMEPAGE, button_state == "down");
     } else if (command == "menu") {
-      OnKeyboardEvent(KEY_MENU, state == "down");
+      OnKeyboardEvent(KEY_MENU, button_state == "down");
     } else if (command == "volumemute") {
-      OnKeyboardEvent(KEY_MUTE, state == "down");
+      OnKeyboardEvent(KEY_MUTE, button_state == "down");
     } else if (command == "volumedown") {
-      OnKeyboardEvent(KEY_VOLUMEDOWN, state == "down");
+      OnKeyboardEvent(KEY_VOLUMEDOWN, button_state == "down");
     } else if (command == "volumeup") {
-      OnKeyboardEvent(KEY_VOLUMEUP, state == "down");
+      OnKeyboardEvent(KEY_VOLUMEUP, button_state == "down");
     } else if (commands_to_custom_action_servers_.find(command) !=
                commands_to_custom_action_servers_.end()) {
       // Simple protocol for commands forwarded to action servers:
       //   - Always 128 bytes
-      //   - Format:   command:state
+      //   - Format:   command:button_state
       //   - Example:  my_button:down
-      std::string action_server_message = command + ":" + state;
+      std::string action_server_message = command + ":" + button_state;
       cuttlefish::WriteAll(commands_to_custom_action_servers_[command],
                            action_server_message.c_str(), 128);
     } else {
-      LOG(WARNING) << "Unsupported control command: " << command << " (" << state << ")";
-      // TODO(b/163081337): Handle custom commands.
+      LOG(WARNING) << "Unsupported control command: " << command << " ("
+                   << button_state << ")";
+    }
+  }
+
+  void OnBluetoothChannelOpen(std::function<bool(const uint8_t *, size_t)>
+                                  bluetooth_message_sender) override {
+    LOG(VERBOSE) << "Bluetooth channel open";
+    bluetooth_handler_.reset(new cuttlefish::webrtc_streaming::BluetoothHandler(
+        cuttlefish::CuttlefishConfig::Get()
+            ->ForDefaultInstance()
+            .rootcanal_test_port(),
+        bluetooth_message_sender));
+  }
+
+  void OnBluetoothMessage(const uint8_t *msg, size_t size) override {
+    bluetooth_handler_->handleMessage(msg, size);
+  }
+
+  void OnCameraData(const std::vector<char> &data) override {
+    if (camera_controller_) {
+      camera_controller_->HandleMessage(data);
     }
   }
 
  private:
   cuttlefish::InputSockets& input_sockets_;
-  cuttlefish::SharedFD kernel_log_events_client_;
+  cuttlefish::KernelLogEventsHandler* kernel_log_events_handler_;
+  int kernel_log_subscription_id_ = -1;
   std::shared_ptr<cuttlefish::webrtc_streaming::AdbHandler> adb_handler_;
-  std::shared_ptr<cuttlefish::webrtc_streaming::KernelLogEventsHandler> kernel_log_events_handler_;
+  std::shared_ptr<cuttlefish::webrtc_streaming::BluetoothHandler>
+      bluetooth_handler_;
   std::map<std::string, cuttlefish::SharedFD> commands_to_custom_action_servers_;
   std::weak_ptr<DisplayHandler> weak_display_handler_;
   std::set<int32_t> active_touch_slots_;
+  cuttlefish::CameraController *camera_controller_;
+  cuttlefish::confui::HostVirtualInput &confui_input_;
 };
 
 CfConnectionObserverFactory::CfConnectionObserverFactory(
-    cuttlefish::InputSockets& input_sockets,
-    cuttlefish::SharedFD kernel_log_events_fd)
+    cuttlefish::InputSockets &input_sockets,
+    cuttlefish::KernelLogEventsHandler* kernel_log_events_handler,
+    cuttlefish::confui::HostVirtualInput &confui_input)
     : input_sockets_(input_sockets),
-      kernel_log_events_fd_(kernel_log_events_fd) {}
+      kernel_log_events_handler_(kernel_log_events_handler),
+      confui_input_{confui_input} {}
 
 std::shared_ptr<cuttlefish::webrtc_streaming::ConnectionObserver>
 CfConnectionObserverFactory::CreateObserver() {
   return std::shared_ptr<cuttlefish::webrtc_streaming::ConnectionObserver>(
-      new ConnectionObserverImpl(input_sockets_,
-                                 kernel_log_events_fd_,
+      new ConnectionObserverImpl(input_sockets_, kernel_log_events_handler_,
                                  commands_to_custom_action_servers_,
-                                 weak_display_handler_));
+                                 weak_display_handler_, camera_controller_,
+                                 confui_input_));
 }
 
 void CfConnectionObserverFactory::AddCustomActionServer(
@@ -313,4 +416,9 @@
     std::weak_ptr<DisplayHandler> display_handler) {
   weak_display_handler_ = display_handler;
 }
+
+void CfConnectionObserverFactory::SetCameraHandler(
+    CameraController *controller) {
+  camera_controller_ = controller;
+}
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc/connection_observer.h b/host/frontend/webrtc/connection_observer.h
index 9ec1e49..f31472c 100644
--- a/host/frontend/webrtc/connection_observer.h
+++ b/host/frontend/webrtc/connection_observer.h
@@ -21,40 +21,55 @@
 
 #include "common/libs/fs/shared_fd.h"
 #include "host/frontend/webrtc/display_handler.h"
+#include "host/frontend/webrtc/kernel_log_events_handler.h"
+#include "host/frontend/webrtc/lib/camera_controller.h"
 #include "host/frontend/webrtc/lib/connection_observer.h"
+#include "host/libs/confui/host_virtual_input.h"
 
 namespace cuttlefish {
 
 struct InputSockets {
-  cuttlefish::SharedFD touch_server;
-  cuttlefish::SharedFD touch_client;
-  cuttlefish::SharedFD keyboard_server;
-  cuttlefish::SharedFD keyboard_client;
-  cuttlefish::SharedFD switches_server;
-  cuttlefish::SharedFD switches_client;
+  SharedFD GetTouchClientByLabel(const std::string& label) {
+    return touch_clients[label];
+  }
+
+  // TODO (b/186773052): Finding strings in a map for every input event may
+  // introduce unwanted latency.
+  std::map<std::string, SharedFD> touch_servers;
+  std::map<std::string, SharedFD> touch_clients;
+  SharedFD keyboard_server;
+  SharedFD keyboard_client;
+  SharedFD switches_server;
+  SharedFD switches_client;
 };
 
 class CfConnectionObserverFactory
-    : public cuttlefish::webrtc_streaming::ConnectionObserverFactory {
+    : public webrtc_streaming::ConnectionObserverFactory {
  public:
-  CfConnectionObserverFactory(cuttlefish::InputSockets& input_sockets,
-                              cuttlefish::SharedFD kernel_log_events_fd);
+  CfConnectionObserverFactory(
+      cuttlefish::InputSockets& input_sockets,
+      KernelLogEventsHandler* kernel_log_events_handler,
+      cuttlefish::confui::HostVirtualInput& confui_input);
   ~CfConnectionObserverFactory() override = default;
 
-  std::shared_ptr<cuttlefish::webrtc_streaming::ConnectionObserver> CreateObserver()
+  std::shared_ptr<webrtc_streaming::ConnectionObserver> CreateObserver()
       override;
 
-  void AddCustomActionServer(cuttlefish::SharedFD custom_action_server_fd,
+  void AddCustomActionServer(SharedFD custom_action_server_fd,
                              const std::vector<std::string>& commands);
 
   void SetDisplayHandler(std::weak_ptr<DisplayHandler> display_handler);
 
+  void SetCameraHandler(CameraController* controller);
+
  private:
-  cuttlefish::InputSockets& input_sockets_;
-  cuttlefish::SharedFD kernel_log_events_fd_;
-  std::map<std::string, cuttlefish::SharedFD>
+  InputSockets& input_sockets_;
+  KernelLogEventsHandler* kernel_log_events_handler_;
+  std::map<std::string, SharedFD>
       commands_to_custom_action_servers_;
   std::weak_ptr<DisplayHandler> weak_display_handler_;
+  cuttlefish::confui::HostVirtualInput& confui_input_;
+  cuttlefish::CameraController* camera_controller_ = nullptr;
 };
 
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc/cvd_video_frame_buffer.cpp b/host/frontend/webrtc/cvd_video_frame_buffer.cpp
index 1582613..8d0aeea 100644
--- a/host/frontend/webrtc/cvd_video_frame_buffer.cpp
+++ b/host/frontend/webrtc/cvd_video_frame_buffer.cpp
@@ -16,6 +16,9 @@
 
 #include "host/frontend/webrtc/cvd_video_frame_buffer.h"
 
+#include <map>
+#include <mutex>
+#include <vector>
 #include "common/libs/utils/size_utils.h"
 
 namespace cuttlefish {
@@ -28,14 +31,42 @@
   return AlignToPowerOf2(width, kLogAlignment);
 }
 
+std::multimap<int, std::vector<uint8_t>> pool;
+std::mutex pool_mutex;
+std::vector<uint8_t> FromPool(int size) {
+  {
+    std::lock_guard<std::mutex> lock(pool_mutex);
+    auto it = pool.find(size);
+    if (it != pool.end()) {
+      auto ret = std::move(it->second);
+      pool.erase(it);
+      return ret;
+    }
+  }
+  return std::vector<uint8_t>(size);
+}
+
+void BackToPool(std::vector<uint8_t> item) {
+  std::lock_guard<std::mutex> lock(pool_mutex);
+  pool.insert({item.size(), std::move(item)});
+}
+
 }  // namespace
 
 CvdVideoFrameBuffer::CvdVideoFrameBuffer(int width, int height)
     : width_(width),
       height_(height),
-      y_(AlignStride(width) * height + kPlanePadding),
-      u_(AlignStride((width + 1) / 2) * ((height + 1) / 2) + kPlanePadding),
-      v_(AlignStride((width + 1) / 2) * ((height + 1) / 2) + kPlanePadding) {}
+      y_(FromPool(AlignStride(width) * height + kPlanePadding)),
+      u_(FromPool(AlignStride((width + 1) / 2) * ((height + 1) / 2) +
+                  kPlanePadding)),
+      v_(FromPool(AlignStride((width + 1) / 2) * ((height + 1) / 2) +
+                  kPlanePadding)) {}
+
+CvdVideoFrameBuffer::~CvdVideoFrameBuffer() {
+  BackToPool(std::move(y_));
+  BackToPool(std::move(u_));
+  BackToPool(std::move(v_));
+}
 
 int CvdVideoFrameBuffer::width() const { return width_; }
 int CvdVideoFrameBuffer::height() const { return height_; }
diff --git a/host/frontend/webrtc/cvd_video_frame_buffer.h b/host/frontend/webrtc/cvd_video_frame_buffer.h
index 70d493b..84b4946 100644
--- a/host/frontend/webrtc/cvd_video_frame_buffer.h
+++ b/host/frontend/webrtc/cvd_video_frame_buffer.h
@@ -31,7 +31,7 @@
   CvdVideoFrameBuffer& operator=(const CvdVideoFrameBuffer& cvd_frame_buf) = default;
   CvdVideoFrameBuffer() = delete;
 
-  ~CvdVideoFrameBuffer() override = default;
+  ~CvdVideoFrameBuffer() override;
 
   int width() const override;
   int height() const override;
diff --git a/host/frontend/webrtc/display_handler.cpp b/host/frontend/webrtc/display_handler.cpp
index 2b4f574..334c35f 100644
--- a/host/frontend/webrtc/display_handler.cpp
+++ b/host/frontend/webrtc/display_handler.cpp
@@ -24,36 +24,27 @@
 
 namespace cuttlefish {
 DisplayHandler::DisplayHandler(
-    std::shared_ptr<webrtc_streaming::VideoSink> display_sink,
-    std::unique_ptr<ScreenConnector> screen_connector)
-    : display_sink_(display_sink), screen_connector_(std::move(screen_connector)) {
-  screen_connector_->SetCallback(std::move(GetScreenConnectorCallback()));
+    std::vector<std::shared_ptr<webrtc_streaming::VideoSink>> display_sinks,
+    ScreenConnector& screen_connector)
+    : display_sinks_(display_sinks), screen_connector_(screen_connector) {
+  screen_connector_.SetCallback(std::move(GetScreenConnectorCallback()));
 }
 
 DisplayHandler::GenerateProcessedFrameCallback DisplayHandler::GetScreenConnectorCallback() {
     // only to tell the producer how to create a ProcessedFrame to cache into the queue
     DisplayHandler::GenerateProcessedFrameCallback callback =
-        [](std::uint32_t display_number, std::uint8_t* frame_pixels,
+        [](std::uint32_t display_number, std::uint32_t frame_width,
+           std::uint32_t frame_height, std::uint32_t frame_stride_bytes,
+           std::uint8_t* frame_pixels,
            WebRtcScProcessedFrame& processed_frame) {
-          // TODO(171305898): handle multiple displays.
-          if (display_number != 0) {
-            processed_frame.is_success_ = false;
-            return;
-          }
-          const int display_w =
-              ScreenConnectorInfo::ScreenWidth(display_number);
-          const int display_h =
-              ScreenConnectorInfo::ScreenHeight(display_number);
-          const int display_stride_bytes =
-              ScreenConnectorInfo::ScreenStrideBytes(display_number);
           processed_frame.display_number_ = display_number;
           processed_frame.buf_ =
-              std::make_unique<CvdVideoFrameBuffer>(display_w, display_h);
+              std::make_unique<CvdVideoFrameBuffer>(frame_width, frame_height);
           libyuv::ABGRToI420(
-              frame_pixels, display_stride_bytes, processed_frame.buf_->DataY(),
+              frame_pixels, frame_stride_bytes, processed_frame.buf_->DataY(),
               processed_frame.buf_->StrideY(), processed_frame.buf_->DataU(),
               processed_frame.buf_->StrideU(), processed_frame.buf_->DataV(),
-              processed_frame.buf_->StrideV(), display_w, display_h);
+              processed_frame.buf_->StrideV(), frame_width, frame_height);
           processed_frame.is_success_ = true;
         };
     return callback;
@@ -61,11 +52,12 @@
 
 [[noreturn]] void DisplayHandler::Loop() {
   for (;;) {
-    auto processed_frame = screen_connector_->OnNextFrame();
+    auto processed_frame = screen_connector_.OnNextFrame();
     // processed_frame has display number from the guest
     {
       std::lock_guard<std::mutex> lock(last_buffer_mutex_);
       std::shared_ptr<CvdVideoFrameBuffer> buffer = std::move(processed_frame.buf_);
+      last_buffer_display_ = processed_frame.display_number_;
       last_buffer_ =
           std::static_pointer_cast<webrtc_streaming::VideoFrameBuffer>(buffer);
     }
@@ -77,9 +69,11 @@
 
 void DisplayHandler::SendLastFrame() {
   std::shared_ptr<webrtc_streaming::VideoFrameBuffer> buffer;
+  std::uint32_t buffer_display;
   {
     std::lock_guard<std::mutex> lock(last_buffer_mutex_);
     buffer = last_buffer_;
+    buffer_display = last_buffer_display_;
   }
   if (!buffer) {
     // If a connection request arrives before the first frame is available don't
@@ -94,21 +88,21 @@
         std::chrono::duration_cast<std::chrono::microseconds>(
             std::chrono::system_clock::now().time_since_epoch())
             .count();
-    display_sink_->OnFrame(buffer, time_stamp);
+    display_sinks_[buffer_display]->OnFrame(buffer, time_stamp);
   }
 }
 
 void DisplayHandler::IncClientCount() {
   client_count_++;
   if (client_count_ == 1) {
-    screen_connector_->ReportClientsConnected(true);
+    screen_connector_.ReportClientsConnected(true);
   }
 }
 
 void DisplayHandler::DecClientCount() {
   client_count_--;
   if (client_count_ == 0) {
-    screen_connector_->ReportClientsConnected(false);
+    screen_connector_.ReportClientsConnected(false);
   }
 }
 
diff --git a/host/frontend/webrtc/display_handler.h b/host/frontend/webrtc/display_handler.h
index 3045e07..7703b08 100644
--- a/host/frontend/webrtc/display_handler.h
+++ b/host/frontend/webrtc/display_handler.h
@@ -16,8 +16,9 @@
 
 #pragma once
 
-#include <mutex>
 #include <memory>
+#include <mutex>
+#include <vector>
 
 #include "host/frontend/webrtc/cvd_video_frame_buffer.h"
 #include "host/frontend/webrtc/lib/video_sink.h"
@@ -33,9 +34,17 @@
  *   must be default-constructable & assignable
  *
  */
-struct WebRtcScProcessedFrame : ScreenConnectorFrameInfo {
+struct WebRtcScProcessedFrame : public ScreenConnectorFrameInfo {
   // must support move semantic
   std::unique_ptr<CvdVideoFrameBuffer> buf_;
+  std::unique_ptr<WebRtcScProcessedFrame> Clone() {
+    // copy internal buffer, not move
+    CvdVideoFrameBuffer* new_buffer = new CvdVideoFrameBuffer(*(buf_.get()));
+    auto cloned_frame = std::make_unique<WebRtcScProcessedFrame>();
+    cloned_frame->buf_ =
+        std::move(std::unique_ptr<CvdVideoFrameBuffer>(new_buffer));
+    return std::move(cloned_frame);
+  }
 };
 
 class DisplayHandler {
@@ -44,8 +53,8 @@
   using GenerateProcessedFrameCallback = ScreenConnector::GenerateProcessedFrameCallback;
 
   DisplayHandler(
-      std::shared_ptr<webrtc_streaming::VideoSink> display_sink,
-      std::unique_ptr<ScreenConnector> screen_connector);
+      std::vector<std::shared_ptr<webrtc_streaming::VideoSink>> display_sinks,
+      ScreenConnector& screen_connector);
   ~DisplayHandler() = default;
 
   [[noreturn]] void Loop();
@@ -56,9 +65,10 @@
 
  private:
   GenerateProcessedFrameCallback GetScreenConnectorCallback();
-  std::shared_ptr<webrtc_streaming::VideoSink> display_sink_;
-  std::unique_ptr<ScreenConnector> screen_connector_;
+  std::vector<std::shared_ptr<webrtc_streaming::VideoSink>> display_sinks_;
+  ScreenConnector& screen_connector_;
   std::shared_ptr<webrtc_streaming::VideoFrameBuffer> last_buffer_;
+  std::uint32_t last_buffer_display_ = 0;
   std::mutex last_buffer_mutex_;
   std::mutex next_frame_mutex_;
   int client_count_ = 0;
diff --git a/host/frontend/webrtc/kernel_log_events_handler.cpp b/host/frontend/webrtc/kernel_log_events_handler.cpp
index 78cdb73..ca14e6f 100644
--- a/host/frontend/webrtc/kernel_log_events_handler.cpp
+++ b/host/frontend/webrtc/kernel_log_events_handler.cpp
@@ -26,13 +26,10 @@
 using namespace android;
 
 namespace cuttlefish {
-namespace webrtc_streaming {
 
 KernelLogEventsHandler::KernelLogEventsHandler(
-    SharedFD kernel_log_fd,
-    std::function<void(const Json::Value&)> send_to_client)
-    : send_to_client_(send_to_client),
-      kernel_log_fd_(kernel_log_fd),
+    SharedFD kernel_log_fd)
+    : kernel_log_fd_(kernel_log_fd),
       eventfd_(SharedFD::Event()),
       running_(true),
       read_thread_([this]() { ReadLoop(); }) {}
@@ -76,22 +73,40 @@
       if (read_result->event == monitor::Event::BootStarted) {
         Json::Value message;
         message["event"] = kBootStartedMessage;
-        send_to_client_(message);
+        DeliverEvent(message);
       }
       if (read_result->event == monitor::Event::BootCompleted) {
         Json::Value message;
         message["event"] = kBootCompletedMessage;
-        send_to_client_(message);
+        DeliverEvent(message);
       }
       if (read_result->event == monitor::Event::ScreenChanged) {
         Json::Value message;
         message["event"] = kScreenChangedMessage;
         message["metadata"] = read_result->metadata;
-        send_to_client_(message);
+        DeliverEvent(message);
       }
     }
   }
 }
 
-}  // namespace webrtc_streaming
+int KernelLogEventsHandler::AddSubscriber(
+    std::function<void(const Json::Value&)> subscriber) {
+  std::lock_guard<std::mutex> lock(subscribers_mtx_);
+  subscribers_[++last_subscriber_id_] = subscriber;
+  return last_subscriber_id_;
+}
+
+void KernelLogEventsHandler::Unsubscribe(int subscriber_id) {
+  std::lock_guard<std::mutex> lock(subscribers_mtx_);
+  subscribers_.erase(subscriber_id);
+}
+
+void KernelLogEventsHandler::DeliverEvent(const Json::Value& event) {
+  std::lock_guard<std::mutex> lock(subscribers_mtx_);
+  for (const auto& entry : subscribers_) {
+    entry.second(event);
+  }
+}
+
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc/kernel_log_events_handler.h b/host/frontend/webrtc/kernel_log_events_handler.h
index 3025f26..5ce99aa 100644
--- a/host/frontend/webrtc/kernel_log_events_handler.h
+++ b/host/frontend/webrtc/kernel_log_events_handler.h
@@ -18,33 +18,35 @@
 
 #include <atomic>
 #include <memory>
+#include <mutex>
 #include <thread>
+#include <map>
 
 #include <json/json.h>
 
 #include "common/libs/fs/shared_fd.h"
 
 namespace cuttlefish {
-namespace webrtc_streaming {
 
 // Listen to kernel log events and report them to clients.
 struct KernelLogEventsHandler {
-  explicit KernelLogEventsHandler(SharedFD kernel_log_fd,
-      std::function<void(const Json::Value&)> send_to_client);
+  explicit KernelLogEventsHandler(SharedFD kernel_log_fd);
 
   ~KernelLogEventsHandler();
 
+  int AddSubscriber(std::function<void(const Json::Value&)> subscriber);
+  void Unsubscribe(int subscriber_id);
  private:
-
-  std::function<void(const Json::Value&)> send_to_client_;
-
   void ReadLoop();
+  void DeliverEvent(const Json::Value& event);
 
   SharedFD kernel_log_fd_;
   SharedFD eventfd_;
   std::atomic<bool> running_;
   std::thread read_thread_;
+  std::map<int, std::function<void(const Json::Value&)>> subscribers_;
+  int last_subscriber_id_ = 0;
+  std::mutex subscribers_mtx_;
 };
 
-}  // namespace webrtc_streaming
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc/lib/camera_controller.h b/host/frontend/webrtc/lib/camera_controller.h
new file mode 100644
index 0000000..403af5b
--- /dev/null
+++ b/host/frontend/webrtc/lib/camera_controller.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <json/json.h>
+
+namespace cuttlefish {
+
+class CameraController {
+ public:
+  virtual ~CameraController() = default;
+
+  // Handle data messages coming from the client
+  virtual void HandleMessage(const std::vector<char>& message) = 0;
+  // Handle control messages coming from the client
+  virtual void HandleMessage(const Json::Value& message) = 0;
+  // Send control messages to client
+  virtual void SendMessage(const Json::Value& msg) {
+    if (message_sender_) {
+      message_sender_(msg);
+    }
+  }
+  virtual void SetMessageSender(
+      std::function<bool(const Json::Value& msg)> sender) {
+    message_sender_ = sender;
+  }
+
+ protected:
+  std::function<bool(const Json::Value& msg)> message_sender_;
+};
+
+}  // namespace cuttlefish
diff --git a/host/frontend/webrtc/lib/camera_streamer.cpp b/host/frontend/webrtc/lib/camera_streamer.cpp
new file mode 100644
index 0000000..7634e28
--- /dev/null
+++ b/host/frontend/webrtc/lib/camera_streamer.cpp
@@ -0,0 +1,162 @@
+/*
+ * 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.
+ */
+#include "camera_streamer.h"
+
+#include <android-base/logging.h>
+#include <chrono>
+#include "common/libs/utils/vsock_connection.h"
+
+namespace cuttlefish {
+namespace webrtc_streaming {
+
+CameraStreamer::CameraStreamer(unsigned int port, unsigned int cid)
+    : cid_(cid), port_(port) {}
+
+CameraStreamer::~CameraStreamer() { Disconnect(); }
+
+// 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()) {
+    // Start new connection
+    pending_connection_ = cvd_connection_.ConnectAsync(port_, cid_);
+    return;
+  } else if (pending_connection_.valid()) {
+    if (!IsConnectionReady()) {
+      return;
+    }
+    std::lock_guard<std::mutex> lock(settings_mutex_);
+    if (!cvd_connection_.WriteMessage(settings_buffer_)) {
+      LOG(ERROR) << "Failed writing camera settings:";
+      return;
+    }
+    StartReadLoop();
+    LOG(INFO) << "Connected!";
+  }
+  auto resolution = resolution_.load();
+  if (resolution.height <= 0 || resolution.width <= 0) {
+    // We don't have a valid resolution that is necessary for
+    // potential frame scaling
+    return;
+  }
+  auto frame = client_frame.video_frame_buffer()->ToI420().get();
+  if (frame->width() != resolution.width ||
+      frame->height() != resolution.height) {
+    // incoming resolution does not match with the resolution we
+    // have communicated to the CVD - scaling required
+    if (!scaled_frame_ || resolution.width != scaled_frame_->width() ||
+        resolution.height != scaled_frame_->height()) {
+      scaled_frame_ =
+          webrtc::I420Buffer::Create(resolution.width, resolution.height);
+    }
+    scaled_frame_->CropAndScaleFrom(*frame);
+    frame = scaled_frame_.get();
+  }
+  if (!VsockSendYUVFrame(frame)) {
+    LOG(ERROR) << "Sending frame over vsock failed";
+  }
+}
+
+// Handle message json coming from client
+void CameraStreamer::HandleMessage(const Json::Value& message) {
+  auto command = message["command"].asString();
+  if (command == "camera_settings") {
+    // save local copy of resolution that is required for frame scaling
+    resolution_ = GetResolutionFromSettings(message);
+    Json::StreamWriterBuilder factory;
+    std::string new_settings = Json::writeString(factory, message);
+    if (!settings_buffer_.empty() && new_settings != settings_buffer_) {
+      // Settings have changed - disconnect
+      // Next incoming frames will trigger re-connection
+      Disconnect();
+    }
+    std::lock_guard<std::mutex> lock(settings_mutex_);
+    settings_buffer_ = new_settings;
+    LOG(INFO) << "New camera settings received:" << new_settings;
+  }
+}
+
+// Handle binary blobs coming from client
+void CameraStreamer::HandleMessage(const std::vector<char>& message) {
+  LOG(INFO) << "Pass through " << message.size() << "bytes";
+  std::lock_guard<std::mutex> lock(frame_mutex_);
+  cvd_connection_.WriteMessage(message);
+}
+
+CameraStreamer::Resolution CameraStreamer::GetResolutionFromSettings(
+    const Json::Value& settings) {
+  return {.width = settings["width"].asInt(),
+          .height = settings["height"].asInt()};
+}
+
+bool CameraStreamer::VsockSendYUVFrame(
+    const webrtc::I420BufferInterface* frame) {
+  int32_t size = frame->width() * frame->height() +
+                 2 * frame->ChromaWidth() * frame->ChromaHeight();
+  const char* y = reinterpret_cast<const char*>(frame->DataY());
+  const char* u = reinterpret_cast<const char*>(frame->DataU());
+  const char* v = reinterpret_cast<const char*>(frame->DataV());
+  auto chroma_width = frame->ChromaWidth();
+  auto chroma_height = frame->ChromaHeight();
+  std::lock_guard<std::mutex> lock(frame_mutex_);
+  return cvd_connection_.Write(size) &&
+         cvd_connection_.WriteStrides(y, frame->width(), frame->height(),
+                                      frame->StrideY()) &&
+         cvd_connection_.WriteStrides(u, chroma_width, chroma_height,
+                                      frame->StrideU()) &&
+         cvd_connection_.WriteStrides(v, chroma_width, chroma_height,
+                                      frame->StrideV());
+}
+
+bool CameraStreamer::IsConnectionReady() {
+  if (!pending_connection_.valid()) {
+    return cvd_connection_.IsConnected();
+  } else if (pending_connection_.wait_for(std::chrono::seconds(0)) !=
+             std::future_status::ready) {
+    // Still waiting for connection
+    return false;
+  } else if (settings_buffer_.empty()) {
+    // connection is ready but we have not yet received client
+    // camera settings
+    return false;
+  }
+  return pending_connection_.get();
+}
+
+void CameraStreamer::StartReadLoop() {
+  if (reader_thread_.joinable()) {
+    reader_thread_.join();
+  }
+  reader_thread_ = std::thread([this] {
+    while (cvd_connection_.IsConnected()) {
+      auto json_value = cvd_connection_.ReadJsonMessage();
+      if (!json_value.empty()) {
+        SendMessage(json_value);
+      }
+    }
+    LOG(INFO) << "Exit reader thread";
+  });
+}
+
+void CameraStreamer::Disconnect() {
+  cvd_connection_.Disconnect();
+  if (reader_thread_.joinable()) {
+    reader_thread_.join();
+  }
+}
+
+}  // namespace webrtc_streaming
+}  // namespace cuttlefish
diff --git a/host/frontend/webrtc/lib/camera_streamer.h b/host/frontend/webrtc/lib/camera_streamer.h
new file mode 100644
index 0000000..ceab2e6
--- /dev/null
+++ b/host/frontend/webrtc/lib/camera_streamer.h
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+#pragma once
+#include <api/video/i420_buffer.h>
+#include <api/video/video_frame.h>
+#include <api/video/video_sink_interface.h>
+#include <json/json.h>
+
+#include "common/libs/utils/vsock_connection.h"
+#include "host/frontend/webrtc/lib/camera_controller.h"
+
+#include <atomic>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+namespace cuttlefish {
+namespace webrtc_streaming {
+
+class CameraStreamer : public rtc::VideoSinkInterface<webrtc::VideoFrame>,
+                       public CameraController {
+ public:
+  CameraStreamer(unsigned int port, unsigned int cid);
+  ~CameraStreamer();
+
+  CameraStreamer(const CameraStreamer& other) = delete;
+  CameraStreamer& operator=(const CameraStreamer& other) = delete;
+
+  void OnFrame(const webrtc::VideoFrame& frame) override;
+
+  void HandleMessage(const Json::Value& message) override;
+  void HandleMessage(const std::vector<char>& message) override;
+
+ private:
+  using Resolution = struct {
+    int32_t width;
+    int32_t height;
+  };
+  bool ForwardClientMessage(const Json::Value& message);
+  Resolution GetResolutionFromSettings(const Json::Value& settings);
+  bool VsockSendYUVFrame(const webrtc::I420BufferInterface* frame);
+  bool IsConnectionReady();
+  void StartReadLoop();
+  void Disconnect();
+  std::future<bool> pending_connection_;
+  VsockClientConnection cvd_connection_;
+  std::atomic<Resolution> resolution_;
+  std::mutex settings_mutex_;
+  std::string settings_buffer_;
+  std::mutex frame_mutex_;
+  std::mutex onframe_mutex_;
+  rtc::scoped_refptr<webrtc::I420Buffer> scaled_frame_;
+  unsigned int cid_;
+  unsigned int port_;
+  std::thread reader_thread_;
+};
+
+}  // namespace webrtc_streaming
+}  // namespace cuttlefish
diff --git a/host/frontend/webrtc/lib/client_handler.cpp b/host/frontend/webrtc/lib/client_handler.cpp
index 4912465..4893f8d 100644
--- a/host/frontend/webrtc/lib/client_handler.cpp
+++ b/host/frontend/webrtc/lib/client_handler.cpp
@@ -38,6 +38,9 @@
 
 static constexpr auto kInputChannelLabel = "input-channel";
 static constexpr auto kAdbChannelLabel = "adb-channel";
+static constexpr auto kBluetoothChannelLabel = "bluetooth-channel";
+static constexpr auto kCameraDataChannelLabel = "camera-data-channel";
+static constexpr auto kCameraDataEof = "EOF";
 
 class CvdCreateSessionDescriptionObserver
     : public webrtc::CreateSessionDescriptionObserver {
@@ -149,6 +152,38 @@
   std::shared_ptr<ConnectionObserver> observer_;
 };
 
+class BluetoothChannelHandler : public webrtc::DataChannelObserver {
+ public:
+  BluetoothChannelHandler(
+      rtc::scoped_refptr<webrtc::DataChannelInterface> bluetooth_channel,
+      std::shared_ptr<ConnectionObserver> observer);
+  ~BluetoothChannelHandler() override;
+
+  void OnStateChange() override;
+  void OnMessage(const webrtc::DataBuffer &msg) override;
+
+ private:
+  rtc::scoped_refptr<webrtc::DataChannelInterface> bluetooth_channel_;
+  std::shared_ptr<ConnectionObserver> observer_;
+  bool channel_open_reported_ = false;
+};
+
+class CameraChannelHandler : public webrtc::DataChannelObserver {
+ public:
+  CameraChannelHandler(
+      rtc::scoped_refptr<webrtc::DataChannelInterface> bluetooth_channel,
+      std::shared_ptr<ConnectionObserver> observer);
+  ~CameraChannelHandler() override;
+
+  void OnStateChange() override;
+  void OnMessage(const webrtc::DataBuffer &msg) override;
+
+ private:
+  rtc::scoped_refptr<webrtc::DataChannelInterface> camera_channel_;
+  std::shared_ptr<ConnectionObserver> observer_;
+  std::vector<char> receive_buffer_;
+};
+
 InputChannelHandler::InputChannelHandler(
     rtc::scoped_refptr<webrtc::DataChannelInterface> input_channel,
     std::shared_ptr<ConnectionObserver> observer)
@@ -271,7 +306,7 @@
     observer_->OnAdbChannelOpen([this](const uint8_t *msg, size_t size) {
       webrtc::DataBuffer buffer(rtc::CopyOnWriteBuffer(msg, size),
                                 true /*binary*/);
-      // TODO (jemoreira): When the SCTP channel is congested data channel
+      // TODO (b/185832105): When the SCTP channel is congested data channel
       // messages are buffered up to 16MB, when the buffer is full the channel
       // is abruptly closed. Keep track of the buffered data to avoid losing the
       // adb data channel.
@@ -320,22 +355,91 @@
   control_channel_->Send(buffer);
 }
 
+BluetoothChannelHandler::BluetoothChannelHandler(
+    rtc::scoped_refptr<webrtc::DataChannelInterface> bluetooth_channel,
+    std::shared_ptr<ConnectionObserver> observer)
+    : bluetooth_channel_(bluetooth_channel), observer_(observer) {
+  bluetooth_channel_->RegisterObserver(this);
+}
+
+BluetoothChannelHandler::~BluetoothChannelHandler() {
+  bluetooth_channel_->UnregisterObserver();
+}
+
+void BluetoothChannelHandler::OnStateChange() {
+  LOG(VERBOSE) << "Bluetooth channel state changed to "
+               << webrtc::DataChannelInterface::DataStateString(
+                      bluetooth_channel_->state());
+}
+
+void BluetoothChannelHandler::OnMessage(const webrtc::DataBuffer &msg) {
+  // Notify bluetooth channel opening when actually using the channel,
+  // it has the same reason with AdbChannelHandler::OnMessage,
+  // to avoid unnecessarily connection for Rootcanal.
+  if (channel_open_reported_ == false) {
+    channel_open_reported_ = true;
+    observer_->OnBluetoothChannelOpen([this](const uint8_t *msg, size_t size) {
+      webrtc::DataBuffer buffer(rtc::CopyOnWriteBuffer(msg, size),
+                                true /*binary*/);
+      // TODO (b/185832105): When the SCTP channel is congested data channel
+      // messages are buffered up to 16MB, when the buffer is full the channel
+      // is abruptly closed. Keep track of the buffered data to avoid losing the
+      // adb data channel.
+      bluetooth_channel_->Send(buffer);
+      return true;
+    });
+  }
+
+  observer_->OnBluetoothMessage(msg.data.cdata(), msg.size());
+}
+
+CameraChannelHandler::CameraChannelHandler(
+    rtc::scoped_refptr<webrtc::DataChannelInterface> camera_channel,
+    std::shared_ptr<ConnectionObserver> observer)
+    : camera_channel_(camera_channel), observer_(observer) {
+  camera_channel_->RegisterObserver(this);
+}
+
+CameraChannelHandler::~CameraChannelHandler() {
+  camera_channel_->UnregisterObserver();
+}
+
+void CameraChannelHandler::OnStateChange() {
+  LOG(VERBOSE) << "Camera channel state changed to "
+               << webrtc::DataChannelInterface::DataStateString(
+                      camera_channel_->state());
+}
+
+void CameraChannelHandler::OnMessage(const webrtc::DataBuffer &msg) {
+  auto msg_data = msg.data.cdata<char>();
+  if (msg.size() == strlen(kCameraDataEof) &&
+      !strncmp(msg_data, kCameraDataEof, msg.size())) {
+    // Send complete buffer to observer on EOF marker
+    observer_->OnCameraData(receive_buffer_);
+    receive_buffer_.clear();
+    return;
+  }
+  // Otherwise buffer up data
+  receive_buffer_.insert(receive_buffer_.end(), msg_data,
+                         msg_data + msg.size());
+}
+
 std::shared_ptr<ClientHandler> ClientHandler::Create(
     int client_id, std::shared_ptr<ConnectionObserver> observer,
     std::function<void(const Json::Value &)> send_to_client_cb,
-    std::function<void()> on_connection_closed_cb) {
+    std::function<void(bool)> on_connection_changed_cb) {
   return std::shared_ptr<ClientHandler>(new ClientHandler(
-      client_id, observer, send_to_client_cb, on_connection_closed_cb));
+      client_id, observer, send_to_client_cb, on_connection_changed_cb));
 }
 
 ClientHandler::ClientHandler(
     int client_id, std::shared_ptr<ConnectionObserver> observer,
     std::function<void(const Json::Value &)> send_to_client_cb,
-    std::function<void()> on_connection_closed_cb)
+    std::function<void(bool)> on_connection_changed_cb)
     : client_id_(client_id),
       observer_(observer),
       send_to_client_(send_to_client_cb),
-      on_connection_closed_cb_(on_connection_closed_cb) {}
+      on_connection_changed_cb_(on_connection_changed_cb) {}
 
 ClientHandler::~ClientHandler() {
   for (auto &data_channel : data_channels_) {
@@ -347,6 +451,13 @@
     rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection) {
   peer_connection_ = peer_connection;
 
+  // libwebrtc configures the video encoder with a start bitrate of just 300kbs
+  // which causes it to drop the first 4 frames it receives. Any value over 2Mbs
+  // will be capped at 2Mbs when passed to the encoder by the peer_connection
+  // object, so we pass the maximum possible value here.
+  webrtc::BitrateSettings bitrate_settings;
+  bitrate_settings.start_bitrate_bps = 2000000; // 2Mbs
+  peer_connection_->SetBitrate(bitrate_settings);
   // At least one data channel needs to be created on the side that makes the
   // SDP offer (the device) for data channels to be enabled at all.
   // This channel is meant to carry control commands from the client.
@@ -388,6 +499,17 @@
   return true;
 }
 
+webrtc::VideoTrackInterface *ClientHandler::GetCameraStream() const {
+  for (const auto &tranceiver : peer_connection_->GetTransceivers()) {
+    auto track = tranceiver->receiver()->track();
+    if (track &&
+        track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) {
+      return static_cast<webrtc::VideoTrackInterface *>(track.get());
+    }
+  }
+  return nullptr;
+}
+
 void ClientHandler::LogAndReplyError(const std::string &error_msg) const {
   LOG(ERROR) << error_msg;
   Json::Value reply;
@@ -545,7 +667,7 @@
   // will then wait for the callback to return -> deadlock). Destroying the
   // peer_connection_ has the same effect. The only alternative is to postpone
   // that operation until after the callback returns.
-  on_connection_closed_cb_();
+  on_connection_changed_cb_(false);
 }
 
 void ClientHandler::OnConnectionChange(
@@ -563,6 +685,7 @@
             control_handler_->Send(msg, size, binary);
             return true;
           });
+      on_connection_changed_cb_(true);
       break;
     case webrtc::PeerConnectionInterface::PeerConnectionState::kDisconnected:
       LOG(VERBOSE) << "Client " << client_id_ << ": Connection disconnected";
@@ -602,6 +725,12 @@
     input_handler_.reset(new InputChannelHandler(data_channel, observer_));
   } else if (label == kAdbChannelLabel) {
     adb_handler_.reset(new AdbChannelHandler(data_channel, observer_));
+  } else if (label == kBluetoothChannelLabel) {
+    bluetooth_handler_.reset(
+        new BluetoothChannelHandler(data_channel, observer_));
+  } else if (label == kCameraDataChannelLabel) {
+    camera_data_handler_.reset(
+        new CameraChannelHandler(data_channel, observer_));
   } else {
     LOG(VERBOSE) << "Data channel connected: " << label;
     data_channels_.push_back(data_channel);
diff --git a/host/frontend/webrtc/lib/client_handler.h b/host/frontend/webrtc/lib/client_handler.h
index b13a502..f7f587b 100644
--- a/host/frontend/webrtc/lib/client_handler.h
+++ b/host/frontend/webrtc/lib/client_handler.h
@@ -21,6 +21,7 @@
 #include <optional>
 #include <sstream>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <json/json.h>
@@ -36,6 +37,8 @@
 class InputChannelHandler;
 class AdbChannelHandler;
 class ControlChannelHandler;
+class BluetoothChannelHandler;
+class CameraChannelHandler;
 
 class ClientHandler : public webrtc::PeerConnectionObserver,
                       public std::enable_shared_from_this<ClientHandler> {
@@ -43,7 +46,7 @@
   static std::shared_ptr<ClientHandler> Create(
       int client_id, std::shared_ptr<ConnectionObserver> observer,
       std::function<void(const Json::Value&)> send_client_cb,
-      std::function<void()> on_connection_closed_cb);
+      std::function<void(bool)> on_connection_changed_cb);
   ~ClientHandler() override;
 
   bool SetPeerConnection(
@@ -55,6 +58,8 @@
   bool AddAudio(rtc::scoped_refptr<webrtc::AudioTrackInterface> track,
                   const std::string& label);
 
+  webrtc::VideoTrackInterface* GetCameraStream() const;
+
   void HandleMessage(const Json::Value& client_message);
 
   // CreateSessionDescriptionObserver implementation
@@ -106,7 +111,7 @@
   };
   ClientHandler(int client_id, std::shared_ptr<ConnectionObserver> observer,
                 std::function<void(const Json::Value&)> send_client_cb,
-                std::function<void()> on_connection_closed_cb);
+                std::function<void(bool)> on_connection_changed_cb);
 
   // Intentionally private, disconnect the client by destroying the object.
   void Close();
@@ -117,12 +122,14 @@
   State state_ = State::kNew;
   std::shared_ptr<ConnectionObserver> observer_;
   std::function<void(const Json::Value&)> send_to_client_;
-  std::function<void()> on_connection_closed_cb_;
+  std::function<void(bool)> on_connection_changed_cb_;
   rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
   std::vector<rtc::scoped_refptr<webrtc::DataChannelInterface>> data_channels_;
   std::unique_ptr<InputChannelHandler> input_handler_;
   std::unique_ptr<AdbChannelHandler> adb_handler_;
   std::unique_ptr<ControlChannelHandler> control_handler_;
+  std::unique_ptr<BluetoothChannelHandler> bluetooth_handler_;
+  std::unique_ptr<CameraChannelHandler> camera_data_handler_;
 };
 
 }  // namespace webrtc_streaming
diff --git a/host/frontend/webrtc/lib/connection_observer.h b/host/frontend/webrtc/lib/connection_observer.h
index 149ffdc..fe82549 100644
--- a/host/frontend/webrtc/lib/connection_observer.h
+++ b/host/frontend/webrtc/lib/connection_observer.h
@@ -42,6 +42,10 @@
   virtual void OnControlChannelOpen(
       std::function<bool(const Json::Value)> control_message_sender) = 0;
   virtual void OnControlMessage(const uint8_t* msg, size_t size) = 0;
+  virtual void OnBluetoothChannelOpen(
+      std::function<bool(const uint8_t*, size_t)> bluetooth_message_sender) = 0;
+  virtual void OnBluetoothMessage(const uint8_t* msg, size_t size) = 0;
+  virtual void OnCameraData(const std::vector<char>& data) = 0;
 };
 
 class ConnectionObserverFactory {
diff --git a/host/frontend/webrtc/lib/streamer.cpp b/host/frontend/webrtc/lib/streamer.cpp
index aa06108..71bbab5 100644
--- a/host/frontend/webrtc/lib/streamer.cpp
+++ b/host/frontend/webrtc/lib/streamer.cpp
@@ -34,6 +34,7 @@
 
 #include "host/frontend/webrtc/lib/audio_device.h"
 #include "host/frontend/webrtc/lib/audio_track_source_impl.h"
+#include "host/frontend/webrtc/lib/camera_streamer.h"
 #include "host/frontend/webrtc/lib/client_handler.h"
 #include "host/frontend/webrtc/lib/port_range_socket_factory.h"
 #include "host/frontend/webrtc/lib/video_track_source_impl.h"
@@ -56,6 +57,9 @@
 constexpr auto kControlPanelButtonTitle = "title";
 constexpr auto kControlPanelButtonIconName = "icon_name";
 constexpr auto kControlPanelButtonShellCommand = "shell_command";
+constexpr auto kControlPanelButtonDeviceStates = "device_states";
+constexpr auto kControlPanelButtonLidSwitchOpen = "lid_switch_open";
+constexpr auto kControlPanelButtonHingeAngleValue = "hinge_angle_value";
 constexpr auto kCustomControlPanelButtonsField = "custom_control_panel_buttons";
 
 void SendJson(WsConnection* ws_conn, const Json::Value& data) {
@@ -100,6 +104,7 @@
   std::string title;
   std::string icon_name;
   std::optional<std::string> shell_command;
+  std::vector<DeviceState> device_states;
 };
 
 // TODO (jemoreira): move to a place in common with the signaling server
@@ -137,6 +142,7 @@
 
   void SendMessageToClient(int client_id, const Json::Value& msg);
   void DestroyClientHandler(int client_id);
+  void SetupCameraForClient(int client_id);
 
   // WsObserver
   void OnOpen() override;
@@ -166,6 +172,7 @@
   std::map<std::string, std::string> hardware_;
   std::vector<ControlPanelButtonDescriptor> custom_control_panel_buttons_;
   std::shared_ptr<AudioDeviceModuleWrapper> audio_device_module_;
+  std::unique_ptr<CameraStreamer> camera_streamer_;
 };
 
 Streamer::Streamer(std::unique_ptr<Streamer::Impl> impl)
@@ -259,16 +266,39 @@
   return impl_->audio_device_module_;
 }
 
+CameraController* Streamer::AddCamera(unsigned int port, unsigned int cid) {
+  impl_->camera_streamer_ = std::make_unique<CameraStreamer>(port, cid);
+  return impl_->camera_streamer_.get();
+}
+
 void Streamer::SetHardwareSpec(std::string key, std::string value) {
   impl_->hardware_.emplace(key, value);
 }
 
-void Streamer::AddCustomControlPanelButton(
+void Streamer::AddCustomControlPanelButton(const std::string& command,
+                                           const std::string& title,
+                                           const std::string& icon_name) {
+  ControlPanelButtonDescriptor button = {
+      .command = command, .title = title, .icon_name = icon_name};
+  impl_->custom_control_panel_buttons_.push_back(button);
+}
+
+void Streamer::AddCustomControlPanelButtonWithShellCommand(
+    const std::string& command, const std::string& title,
+    const std::string& icon_name, const std::string& shell_command) {
+  ControlPanelButtonDescriptor button = {
+      .command = command, .title = title, .icon_name = icon_name};
+  button.shell_command = shell_command;
+  impl_->custom_control_panel_buttons_.push_back(button);
+}
+
+void Streamer::AddCustomControlPanelButtonWithDeviceStates(
     const std::string& command, const std::string& title,
     const std::string& icon_name,
-    const std::optional<std::string>& shell_command) {
-  ControlPanelButtonDescriptor button = {command, title, icon_name,
-                                         shell_command};
+    const std::vector<DeviceState>& device_states) {
+  ControlPanelButtonDescriptor button = {
+      .command = command, .title = title, .icon_name = icon_name};
+  button.device_states = device_states;
   impl_->custom_control_panel_buttons_.push_back(button);
 }
 
@@ -359,6 +389,21 @@
       button_entry[kControlPanelButtonIconName] = button.icon_name;
       if (button.shell_command) {
         button_entry[kControlPanelButtonShellCommand] = *(button.shell_command);
+      } else if (!button.device_states.empty()) {
+        Json::Value device_states(Json::arrayValue);
+        for (const DeviceState& device_state : button.device_states) {
+          Json::Value device_state_entry;
+          if (device_state.lid_switch_open) {
+            device_state_entry[kControlPanelButtonLidSwitchOpen] =
+                *device_state.lid_switch_open;
+          }
+          if (device_state.hinge_angle_value) {
+            device_state_entry[kControlPanelButtonHingeAngleValue] =
+                *device_state.hinge_angle_value;
+          }
+          device_states.append(device_state_entry);
+        }
+        button_entry[kControlPanelButtonDeviceStates] = device_states;
       }
       custom_control_panel_buttons.append(button_entry);
     }
@@ -529,7 +574,13 @@
       [this, client_id](const Json::Value& msg) {
         SendMessageToClient(client_id, msg);
       },
-      [this, client_id] { DestroyClientHandler(client_id); });
+      [this, client_id](bool isOpen) {
+        if (isOpen) {
+          SetupCameraForClient(client_id);
+        } else {
+          DestroyClientHandler(client_id);
+        }
+      });
 
   webrtc::PeerConnectionInterface::RTCConfiguration config;
   config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
@@ -604,5 +655,19 @@
   });
 }
 
+void Streamer::Impl::SetupCameraForClient(int client_id) {
+  if (!camera_streamer_) {
+    return;
+  }
+  auto client_handler = clients_[client_id];
+  if (client_handler) {
+    auto camera_track = client_handler->GetCameraStream();
+    if (camera_track) {
+      camera_track->AddOrUpdateSink(camera_streamer_.get(),
+                                    rtc::VideoSinkWants());
+    }
+  }
+}
+
 }  // namespace webrtc_streaming
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc/lib/streamer.h b/host/frontend/webrtc/lib/streamer.h
index 90f20e4..50a7e12 100644
--- a/host/frontend/webrtc/lib/streamer.h
+++ b/host/frontend/webrtc/lib/streamer.h
@@ -24,8 +24,11 @@
 #include <utility>
 #include <vector>
 
+#include "host/libs/config/custom_actions.h"
+
 #include "host/frontend/webrtc/lib/audio_sink.h"
 #include "host/frontend/webrtc/lib/audio_source.h"
+#include "host/frontend/webrtc/lib/camera_controller.h"
 #include "host/frontend/webrtc/lib/connection_observer.h"
 #include "host/frontend/webrtc/lib/local_recorder.h"
 #include "host/frontend/webrtc/lib/video_sink.h"
@@ -96,13 +99,19 @@
   // stream here.
   std::shared_ptr<AudioSource> GetAudioSource();
 
+  CameraController* AddCamera(unsigned int port, unsigned int cid);
+
   // Add a custom button to the control panel.
-  //   If this button should be handled by an action server, use nullopt (the
-  //   default) for shell_command.
-  void AddCustomControlPanelButton(
+  void AddCustomControlPanelButton(const std::string& command,
+                                   const std::string& title,
+                                   const std::string& icon_name);
+  void AddCustomControlPanelButtonWithShellCommand(
+      const std::string& command, const std::string& title,
+      const std::string& icon_name, const std::string& shell_command);
+  void AddCustomControlPanelButtonWithDeviceStates(
       const std::string& command, const std::string& title,
       const std::string& icon_name,
-      const std::optional<std::string>& shell_command = std::nullopt);
+      const std::vector<DeviceState>& device_states);
 
   // Register with the operator.
   void Register(std::weak_ptr<OperatorObserver> operator_observer);
diff --git a/host/frontend/webrtc/lib/utils.cpp b/host/frontend/webrtc/lib/utils.cpp
index 117492d..78460c3 100644
--- a/host/frontend/webrtc/lib/utils.cpp
+++ b/host/frontend/webrtc/lib/utils.cpp
@@ -23,23 +23,47 @@
 namespace cuttlefish {
 namespace webrtc_streaming {
 
+namespace {
+
+std::string ValidateField(const Json::Value &obj, const std::string &type,
+                          const std::string &field_name,
+                          const Json::ValueType &field_type, bool required) {
+  if (!obj.isMember(field_name) && !required) {
+    return "";
+  }
+  if (!(obj.isMember(field_name) &&
+        obj[field_name].isConvertibleTo(field_type))) {
+    std::string error_msg = "Expected a field named '";
+    error_msg += field_name + "' of type '";
+    error_msg += std::to_string(field_type);
+    error_msg += "'";
+    if (!type.empty()) {
+      error_msg += " in message of type '" + type + "'";
+    }
+    error_msg += ".";
+    return error_msg;
+  }
+  return "";
+}
+
+}  // namespace
+
 ValidationResult ValidationResult::ValidateJsonObject(
     const Json::Value &obj, const std::string &type,
-    const std::map<std::string, Json::ValueType> &fields) {
-  for (const auto &field_spec : fields) {
-    const auto &field_name = field_spec.first;
-    auto field_type = field_spec.second;
-    if (!(obj.isMember(field_name) &&
-          obj[field_name].isConvertibleTo(field_type))) {
-      std::string error_msg = "Expected a field named '";
-      error_msg += field_name + "' of type '";
-      error_msg += std::to_string(field_type);
-      error_msg += "'";
-      if (!type.empty()) {
-        error_msg += " in message of type '" + type + "'";
-      }
-      error_msg += ".";
-      return {error_msg};
+    const std::map<std::string, Json::ValueType> &required_fields,
+    const std::map<std::string, Json::ValueType> &optional_fields) {
+  for (const auto &field_spec : required_fields) {
+    auto result =
+        ValidateField(obj, type, field_spec.first, field_spec.second, true);
+    if (!result.empty()) {
+      return {result};
+    }
+  }
+  for (const auto &field_spec : optional_fields) {
+    auto result =
+        ValidateField(obj, type, field_spec.first, field_spec.second, false);
+    if (!result.empty()) {
+      return {result};
     }
   }
   return {};
diff --git a/host/frontend/webrtc/lib/utils.h b/host/frontend/webrtc/lib/utils.h
index 1208551..169221c 100644
--- a/host/frontend/webrtc/lib/utils.h
+++ b/host/frontend/webrtc/lib/utils.h
@@ -32,8 +32,9 @@
   // Helper method to ensure a json object has the required fields convertible
   // to the appropriate types.
   static ValidationResult ValidateJsonObject(
-    const Json::Value &obj, const std::string &type,
-    const std::map<std::string, Json::ValueType> &fields);
+      const Json::Value &obj, const std::string &type,
+      const std::map<std::string, Json::ValueType> &required_fields,
+      const std::map<std::string, Json::ValueType> &optional_fields = {});
 
   bool ok() const { return !error_.has_value(); }
   std::string error() const { return error_.value_or(""); }
diff --git a/host/frontend/webrtc/main.cpp b/host/frontend/webrtc/main.cpp
index f9b29fd..052cce5 100644
--- a/host/frontend/webrtc/main.cpp
+++ b/host/frontend/webrtc/main.cpp
@@ -32,14 +32,20 @@
 #include "host/frontend/webrtc/audio_handler.h"
 #include "host/frontend/webrtc/connection_observer.h"
 #include "host/frontend/webrtc/display_handler.h"
+#include "host/frontend/webrtc/kernel_log_events_handler.h"
+#include "host/frontend/webrtc/lib/camera_controller.h"
 #include "host/frontend/webrtc/lib/local_recorder.h"
 #include "host/frontend/webrtc/lib/streamer.h"
+#include "host/frontend/webrtc/lib/video_sink.h"
 #include "host/libs/audio_connector/server.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/logging.h"
+#include "host/libs/confui/host_mode_ctrl.h"
+#include "host/libs/confui/host_server.h"
 #include "host/libs/screen_connector/screen_connector.h"
 
-DEFINE_int32(touch_fd, -1, "An fd to listen on for touch connections.");
+DEFINE_string(touch_fds, "",
+              "A list of fds to listen on for touch connections.");
 DEFINE_int32(keyboard_fd, -1, "An fd to listen on for keyboard connections.");
 DEFINE_int32(switches_fd, -1, "An fd to listen on for switch connections.");
 DEFINE_int32(frame_server_fd, -1, "An fd to listen on for frame updates");
@@ -52,13 +58,16 @@
 DEFINE_bool(write_virtio_input, true,
             "Whether to send input events in virtio format.");
 DEFINE_int32(audio_server_fd, -1, "An fd to listen on for audio frames");
+DEFINE_int32(camera_streamer_fd, -1, "An fd to send client camera frames");
 
 using cuttlefish::AudioHandler;
 using cuttlefish::CfConnectionObserverFactory;
 using cuttlefish::DisplayHandler;
+using cuttlefish::KernelLogEventsHandler;
 using cuttlefish::webrtc_streaming::LocalRecorder;
 using cuttlefish::webrtc_streaming::Streamer;
 using cuttlefish::webrtc_streaming::StreamerConfig;
+using cuttlefish::webrtc_streaming::VideoSink;
 
 class CfOperatorObserver
     : public cuttlefish::webrtc_streaming::OperatorObserver {
@@ -68,10 +77,10 @@
     LOG(VERBOSE) << "Registered with Operator";
   }
   virtual void OnClose() override {
-    LOG(FATAL) << "Connection with Operator unexpectedly closed";
+    LOG(ERROR) << "Connection with Operator unexpectedly closed";
   }
   virtual void OnError() override {
-    LOG(FATAL) << "Error encountered in connection with Operator";
+    LOG(ERROR) << "Error encountered in connection with Operator";
   }
 };
 
@@ -128,11 +137,16 @@
 
   cuttlefish::InputSockets input_sockets;
 
-  input_sockets.touch_server = cuttlefish::SharedFD::Dup(FLAGS_touch_fd);
+  auto counter = 0;
+  for (const auto& touch_fd_str : android::base::Split(FLAGS_touch_fds, ",")) {
+    auto touch_fd = std::stoi(touch_fd_str);
+    input_sockets.touch_servers["display_" + std::to_string(counter++)] =
+        cuttlefish::SharedFD::Dup(touch_fd);
+    close(touch_fd);
+  }
   input_sockets.keyboard_server = cuttlefish::SharedFD::Dup(FLAGS_keyboard_fd);
   input_sockets.switches_server = cuttlefish::SharedFD::Dup(FLAGS_switches_fd);
   auto control_socket = cuttlefish::SharedFD::Dup(FLAGS_command_fd);
-  close(FLAGS_touch_fd);
   close(FLAGS_keyboard_fd);
   close(FLAGS_switches_fd);
   close(FLAGS_command_fd);
@@ -141,19 +155,25 @@
   // devices have been initialized. That's OK though, because without those
   // devices there is no meaningful interaction the user can have with the
   // device.
-  input_sockets.touch_client =
-      cuttlefish::SharedFD::Accept(*input_sockets.touch_server);
+  for (const auto& touch_entry : input_sockets.touch_servers) {
+    input_sockets.touch_clients[touch_entry.first] =
+        cuttlefish::SharedFD::Accept(*touch_entry.second);
+  }
   input_sockets.keyboard_client =
       cuttlefish::SharedFD::Accept(*input_sockets.keyboard_server);
   input_sockets.switches_client =
       cuttlefish::SharedFD::Accept(*input_sockets.switches_server);
 
-  std::thread touch_accepter([&input_sockets]() {
-    for (;;) {
-      input_sockets.touch_client =
-          cuttlefish::SharedFD::Accept(*input_sockets.touch_server);
-    }
-  });
+  std::vector<std::thread> touch_accepters;
+  for (const auto& touch : input_sockets.touch_servers) {
+    auto label = touch.first;
+    touch_accepters.emplace_back([label, &input_sockets]() {
+      for (;;) {
+        input_sockets.touch_clients[label] =
+            cuttlefish::SharedFD::Accept(*input_sockets.touch_servers[label]);
+      }
+    });
+  }
   std::thread keyboard_accepter([&input_sockets]() {
     for (;;) {
       input_sockets.keyboard_client =
@@ -173,8 +193,16 @@
 
   auto cvd_config = cuttlefish::CuttlefishConfig::Get();
   auto instance = cvd_config->ForDefaultInstance();
-  auto screen_connector =
-      cuttlefish::DisplayHandler::ScreenConnector::Get(FLAGS_frame_server_fd);
+  auto& host_mode_ctrl = cuttlefish::HostModeCtrl::Get();
+  auto screen_connector_ptr = cuttlefish::DisplayHandler::ScreenConnector::Get(
+      FLAGS_frame_server_fd, host_mode_ctrl);
+  auto& screen_connector = *(screen_connector_ptr.get());
+
+  // create confirmation UI service, giving host_mode_ctrl and
+  // screen_connector
+  // keep this singleton object alive until the webRTC process ends
+  static auto& host_confui_server =
+      cuttlefish::confui::HostServer::Get(host_mode_ctrl, screen_connector);
 
   StreamerConfig streamer_config;
 
@@ -184,27 +212,49 @@
   streamer_config.operator_server.addr = cvd_config->sig_server_address();
   streamer_config.operator_server.port = cvd_config->sig_server_port();
   streamer_config.operator_server.path = cvd_config->sig_server_path();
-  streamer_config.operator_server.security =
-      cvd_config->sig_server_strict()
-          ? WsConnection::Security::kStrict
-          : WsConnection::Security::kAllowSelfSigned;
+  if (cvd_config->sig_server_secure()) {
+    streamer_config.operator_server.security =
+        cvd_config->sig_server_strict()
+            ? WsConnection::Security::kStrict
+            : WsConnection::Security::kAllowSelfSigned;
+  } else {
+    streamer_config.operator_server.security =
+        WsConnection::Security::kInsecure;
+  }
 
   if (!cvd_config->sig_server_headers_path().empty()) {
     streamer_config.operator_server.http_headers =
         ParseHttpHeaders(cvd_config->sig_server_headers_path());
   }
 
+  KernelLogEventsHandler kernel_logs_event_handler(kernel_log_events_client);
   auto observer_factory = std::make_shared<CfConnectionObserverFactory>(
-      input_sockets, kernel_log_events_client);
+      input_sockets, &kernel_logs_event_handler, host_confui_server);
 
   auto streamer = Streamer::Create(streamer_config, observer_factory);
   CHECK(streamer) << "Could not create streamer";
 
-  auto display_0 = streamer->AddDisplay(
-      "display_0", screen_connector->ScreenWidth(0),
-      screen_connector->ScreenHeight(0), cvd_config->dpi(), true);
+  uint32_t display_index = 0;
+  std::vector<std::shared_ptr<VideoSink>> displays;
+  for (const auto& display_config : cvd_config->display_configs()) {
+    const std::string display_id = "display_" + std::to_string(display_index);
+
+    auto display =
+        streamer->AddDisplay(display_id, display_config.width,
+                             display_config.height, display_config.dpi, true);
+    displays.push_back(display);
+
+    ++display_index;
+  }
+
   auto display_handler =
-    std::make_shared<DisplayHandler>(display_0, std::move(screen_connector));
+      std::make_shared<DisplayHandler>(std::move(displays), screen_connector);
+
+  if (instance.camera_server_port()) {
+    auto camera_controller = streamer->AddCamera(instance.camera_server_port(),
+                                                 instance.vsock_guest_cid());
+    observer_factory->SetCameraHandler(camera_controller);
+  }
 
   std::unique_ptr<cuttlefish::webrtc_streaming::LocalRecorder> local_recorder;
   if (cvd_config->record_screen()) {
@@ -272,11 +322,10 @@
                    << *(custom_action.shell_command);
       }
       const auto button = custom_action.buttons[0];
-      streamer->AddCustomControlPanelButton(button.command, button.title,
-                                            button.icon_name,
-                                            custom_action.shell_command);
-    }
-    if (custom_action.server) {
+      streamer->AddCustomControlPanelButtonWithShellCommand(
+          button.command, button.title, button.icon_name,
+          *(custom_action.shell_command));
+    } else if (custom_action.server) {
       if (action_server_fds.find(*(custom_action.server)) !=
           action_server_fds.end()) {
         LOG(INFO) << "Connecting to custom action server "
@@ -303,6 +352,15 @@
         LOG(ERROR) << "Custom action server not provided as command line flag: "
                    << *(custom_action.server);
       }
+    } else if (!custom_action.device_states.empty()) {
+      if (custom_action.buttons.size() != 1) {
+        LOG(FATAL)
+            << "Expected exactly one button for custom action device states.";
+      }
+      const auto button = custom_action.buttons[0];
+      streamer->AddCustomControlPanelButtonWithDeviceStates(
+          button.command, button.title, button.icon_name,
+          custom_action.device_states);
     }
   }
 
@@ -332,6 +390,7 @@
   if (audio_handler) {
     audio_handler->Start();
   }
+  host_confui_server.Start();
   display_handler->Loop();
 
   return 0;
diff --git a/host/frontend/webrtc_operator/Android.bp b/host/frontend/webrtc_operator/Android.bp
index 5c0f888..54eda44 100644
--- a/host/frontend/webrtc_operator/Android.bp
+++ b/host/frontend/webrtc_operator/Android.bp
@@ -109,6 +109,13 @@
 }
 
 prebuilt_usr_share_host {
+    name: "webrtc_rootcanal.js",
+    src: "assets/js/rootcanal.js",
+    filename: "rootcanal.js",
+    sub_dir: "webrtc/assets/js",
+}
+
+prebuilt_usr_share_host {
     name: "webrtc_server.crt",
     src: "certs/server.crt",
     filename: "server.crt",
diff --git a/host/frontend/webrtc_operator/assets/controls.css b/host/frontend/webrtc_operator/assets/controls.css
index 5bb0cce..a2dce53 100644
--- a/host/frontend/webrtc_operator/assets/controls.css
+++ b/host/frontend/webrtc_operator/assets/controls.css
@@ -19,15 +19,15 @@
   padding-right: 7px;
   border-radius: 10px;
   background-color: #5f6368; /* Google grey 700 */
-  width: 117px;
-  height: 64px;
+  width: 80px;
+  height: 44px;
 }
 
 .toggle-control .toggle-control-icon {
   position: relative;
   display: inline-block;
   float: left;
-  font-size: 64px;
+  font-size: 44px;
   color: #e8eaed;
 }
 
@@ -35,9 +35,9 @@
   position: relative;
   display: inline-block;
   float:left;
-  width: 52px;
-  height: 30px;
-  top: 17px;
+  width: 36px;
+  height: 21px;
+  top: 11px;
 }
 
 .toggle-control .toggle-control-switch input {
@@ -55,16 +55,16 @@
   bottom: 0;
   -webkit-transition: .4s;
   transition: .4s;
-  border-radius: 30px;
-  border: solid 5px;
+  border-radius: 21px;
+  border: solid 4px;
   border-color: #e8eaed;
 }
 
 .toggle-control .toggle-control-slider:before {
   position: absolute;
   content: "";
-  height: 18px;
-  width: 18px;
+  height: 12px;
+  width: 12px;
   left: 1px;
   bottom: 1px;
   background-color: #e8eaed;
@@ -82,7 +82,7 @@
 }
 
 .toggle-control input:checked + .toggle-control-slider:before {
-  -webkit-transform: translateX(22px);
-  -ms-transform: translateX(22px);
-  transform: translateX(22px);
+  -webkit-transform: translateX(15px);
+  -ms-transform: translateX(15px);
+  transform: translateX(15px);
 }
diff --git a/host/frontend/webrtc_operator/assets/index.html b/host/frontend/webrtc_operator/assets/index.html
index 7ab6a72..080718e 100644
--- a/host/frontend/webrtc_operator/assets/index.html
+++ b/host/frontend/webrtc_operator/assets/index.html
@@ -22,9 +22,11 @@
         <link rel="stylesheet" type="text/css" href="controls.css" >
         <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined">
         <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
+        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
     </head>
 
     <body>
+      <div id="error-message-div"><h3 id="error-message" class="error"></h3></div>
       <section id='device-selector'>
         <h1>Available devices <span id='refresh-list'>&#8635;</span></h1>
         <ul id="device-list"></ul>
@@ -40,29 +42,43 @@
             <h3 id='status-message' class='connecting'>Connecting to device</h3>
           </div>
         </div>
-        <div id='controls-and-screens'>
+        <div id='controls-and-displays'>
           <div id='control-panel-default-buttons' class='control-panel-column'>
             <button id='device-details-button' title='Device Details' class='material-icons'>
               settings
             </button>
+            <button id='bluetooth-console-button' title='Bluetooth console' class='material-icons'>
+              settings_bluetooth
+            </button>
           </div>
           <div id='control-panel-custom-buttons' class='control-panel-column'></div>
-          <div id='screens'>
-            <video id="device-screen" autoplay ></video>
+          <div id='device-displays'>
           </div>
         </div>
       </section>
-      <div id='device-details-modal'>
-        <div id='device-details-modal-header'>
+      <div id='device-details-modal' class='modal'>
+        <div id='device-details-modal-header' class='modal-header'>
           <h2>Device Details</h2>
-          <button id='device-details-close' title='Close' class='material-icons'>close</button>
+          <button id='device-details-close' title='Close' class='material-icons modal-close'>close</button>
         </div>
         <hr>
         <h3>Hardware Configuration</h3>
         <span id='device-details-hardware'>unknown</span>
       </div>
-
+      <div id='bluetooth-console-modal' class='modal'>
+        <div id='bluetooth-console-modal-header' class='modal-header'>
+          <h2>Bluetooth Console</h2>
+          <button id='bluetooth-console-close' title='Close' class='material-icons modal-close'>close</button>
+        </div>
+        <div>
+          <table>
+            <tr><td colspan='2'><textarea id='bluetooth-console-view' readonly rows='10' cols='60'></textarea></td></tr>
+            <tr><td width='1'><p id='bluetooth-console-cmd-label'>Command:</p></td><td width='100'><input id='bluetooth-console-input' type='text'></input></td></tr>
+          </table>
+        </div>
+      </div>
        <script src="js/adb.js"></script>
+       <script src="js/rootcanal.js"></script>
        <script src="js/cf_webrtc.js" type="module"></script>
        <script src="js/controls.js"></script>
        <script src="js/app.js"></script>
diff --git a/host/frontend/webrtc_operator/assets/js/adb.js b/host/frontend/webrtc_operator/assets/js/adb.js
index 75540ae..b011114 100644
--- a/host/frontend/webrtc_operator/assets/js/adb.js
+++ b/host/frontend/webrtc_operator/assets/js/adb.js
@@ -29,69 +29,67 @@
 let array = new Uint8Array();
 
 function setU32LE(array, offset, x) {
-    array[offset] = x & 0xff;
-    array[offset + 1] = (x >> 8) & 0xff;
-    array[offset + 2] = (x >> 16) & 0xff;
-    array[offset + 3] = x >> 24;
+  array[offset] = x & 0xff;
+  array[offset + 1] = (x >> 8) & 0xff;
+  array[offset + 2] = (x >> 16) & 0xff;
+  array[offset + 3] = x >> 24;
 }
 
 function getU32LE(array, offset) {
-    let x = array[offset]
-        | (array[offset + 1] << 8)
-        | (array[offset + 2] << 16)
-        | (array[offset + 3] << 24);
+  let x = array[offset] | (array[offset + 1] << 8) | (array[offset + 2] << 16) |
+      (array[offset + 3] << 24);
 
-    return x >>> 0;  // convert signed to unsigned if necessary.
+  return x >>> 0;  // convert signed to unsigned if necessary.
 }
 
 function computeChecksum(array) {
-    let sum = 0;
-    let i;
-    for (i = 0; i < array.length; ++i) {
-        sum = ((sum + array[i]) & 0xffffffff) >>> 0;
-    }
+  let sum = 0;
+  let i;
+  for (i = 0; i < array.length; ++i) {
+    sum = ((sum + array[i]) & 0xffffffff) >>> 0;
+  }
 
-    return sum;
+  return sum;
 }
 
 function createAdbMessage(command, arg0, arg1, payload) {
-    let arrayBuffer = new ArrayBuffer(24 + payload.length);
-    let array = new Uint8Array(arrayBuffer);
-    setU32LE(array, 0, command);
-    setU32LE(array, 4, arg0);
-    setU32LE(array, 8, arg1);
-    setU32LE(array, 12, payload.length);
-    setU32LE(array, 16, computeChecksum(payload));
-    setU32LE(array, 20, command ^ 0xffffffff);
-    array.set(payload, 24);
+  let arrayBuffer = new ArrayBuffer(24 + payload.length);
+  let array = new Uint8Array(arrayBuffer);
+  setU32LE(array, 0, command);
+  setU32LE(array, 4, arg0);
+  setU32LE(array, 8, arg1);
+  setU32LE(array, 12, payload.length);
+  setU32LE(array, 16, computeChecksum(payload));
+  setU32LE(array, 20, command ^ 0xffffffff);
+  array.set(payload, 24);
 
-    return arrayBuffer;
+  return arrayBuffer;
 }
 
 function adbOpenConnection() {
-    let systemIdentity = utf8Encoder.encode("Cray_II:1234:whatever");
+  let systemIdentity = utf8Encoder.encode('Cray_II:1234:whatever');
 
-    let arrayBuffer = createAdbMessage(
-        A_CNXN, 0x1000000, 256 * 1024, systemIdentity);
+  let arrayBuffer =
+      createAdbMessage(A_CNXN, 0x1000000, 256 * 1024, systemIdentity);
 
-    adb_ws.send(arrayBuffer);
+  adb_ws.send(arrayBuffer);
 }
 
 function adbShell(command) {
-    let destination = utf8Encoder.encode("shell:" + command);
+  let destination = utf8Encoder.encode('shell:' + command);
 
-    let arrayBuffer = createAdbMessage(A_OPEN, kLocalChannelId, 0, destination);
-    adb_ws.send(arrayBuffer);
-    awaitConnection();
+  let arrayBuffer = createAdbMessage(A_OPEN, kLocalChannelId, 0, destination);
+  adb_ws.send(arrayBuffer);
+  awaitConnection();
 }
 
 function adbSendOkay(remoteId) {
-    let payload = new Uint8Array(0);
+  let payload = new Uint8Array(0);
 
-    let arrayBuffer = createAdbMessage(
-        A_OKAY, kLocalChannelId, remoteId, payload);
+  let arrayBuffer =
+      createAdbMessage(A_OKAY, kLocalChannelId, remoteId, payload);
 
-    adb_ws.send(arrayBuffer);
+  adb_ws.send(arrayBuffer);
 }
 
 function JoinArrays(arr1, arr2) {
@@ -101,10 +99,12 @@
   return arr;
 }
 
-// Simple lifecycle management that executes callbacks based on connection state.
+// Simple lifecycle management that executes callbacks based on connection
+// state.
 //
-// Any attempt to initiate a command (e.g. creating a connection, sending a message)
-// (re)starts a timer. Any response back from any command stops that timer.
+// Any attempt to initiate a command (e.g. creating a connection, sending a
+// message) (re)starts a timer. Any response back from any command stops that
+// timer.
 const timeoutMs = 3000;
 let connectedCb;
 let disconnectedCb;
@@ -125,80 +125,77 @@
 }
 
 function adbOnMessage(arrayBuffer) {
-    // console.log("adb_ws: onmessage (" + arrayBuffer.byteLength + " bytes)");
-    array = JoinArrays(array, new Uint8Array(arrayBuffer));
+  // console.debug("adb_ws: onmessage (" + arrayBuffer.byteLength + " bytes)");
+  array = JoinArrays(array, new Uint8Array(arrayBuffer));
 
-    while (array.length > 0) {
-        if (array.length < 24) {
-            // Incomplete package, must wait for more data.
-            return;
-        }
-
-        let command = getU32LE(array, 0);
-        let magic = getU32LE(array, 20);
-
-        if (command != ((magic ^ 0xffffffff) >>> 0)) {
-            console.log("command = " + command + ", magic = " + magic);
-            console.log("adb message command vs magic failed.");
-            return;
-        }
-
-        let payloadLength = getU32LE(array, 12);
-
-        if (array.length < 24 + payloadLength) {
-            // Incomplete package, must wait for more data.
-            return;
-        }
-
-        let payloadChecksum = getU32LE(array, 16);
-        let checksum = computeChecksum(array.slice(24));
-
-        if (payloadChecksum != checksum) {
-            console.log("adb message checksum mismatch.");
-            // This can happen if a shell command executes while another
-            // channel is receiving data.
-        }
-
-        switch (command) {
-            case A_CNXN:
-            {
-                console.log("WebRTC adb connected.");
-                connected();
-                break;
-            }
-
-            case A_OKAY:
-            {
-                let remoteId = getU32LE(array, 4);
-                console.log("WebRTC adb channel created w/ remoteId " + remoteId);
-                connected();
-                break;
-            }
-
-            case A_WRTE:
-            {
-                let remoteId = getU32LE(array, 4);
-                adbSendOkay(remoteId);
-                break;
-            }
-        }
-        array = array.subarray(24 + payloadLength, array.length);
+  while (array.length > 0) {
+    if (array.length < 24) {
+      // Incomplete package, must wait for more data.
+      return;
     }
+
+    let command = getU32LE(array, 0);
+    let magic = getU32LE(array, 20);
+
+    if (command != ((magic ^ 0xffffffff) >>> 0)) {
+      console.error('adb message command vs magic failed.');
+      console.error('command = ' + command + ', magic = ' + magic);
+      return;
+    }
+
+    let payloadLength = getU32LE(array, 12);
+
+    if (array.length < 24 + payloadLength) {
+      // Incomplete package, must wait for more data.
+      return;
+    }
+
+    let payloadChecksum = getU32LE(array, 16);
+    let checksum = computeChecksum(array.slice(24));
+
+    if (payloadChecksum != checksum) {
+      console.error('adb message checksum mismatch.');
+      // This can happen if a shell command executes while another
+      // channel is receiving data.
+    }
+
+    switch (command) {
+      case A_CNXN: {
+        console.info('WebRTC adb connected.');
+        connected();
+        break;
+      }
+
+      case A_OKAY: {
+        let remoteId = getU32LE(array, 4);
+        console.debug('WebRTC adb channel created w/ remoteId ' + remoteId);
+        connected();
+        break;
+      }
+
+      case A_WRTE: {
+        let remoteId = getU32LE(array, 4);
+        adbSendOkay(remoteId);
+        break;
+      }
+    }
+    array = array.subarray(24 + payloadLength, array.length);
+  }
 }
 
 function init_adb(devConn, ccb = connectedCb, dcb = disconnectedCb) {
-    if (adb_ws) return;
+  if (adb_ws) return;
 
-    adb_ws = {
-      send: function(buffer) {
-        devConn.sendAdbMessage(buffer);
-      }
-    };
-    connectedCb = ccb;
-    disconnectedCb = dcb;
-    awaitConnection();
+  adb_ws = {
+    send: function(buffer) {
+      devConn.sendAdbMessage(buffer);
+    }
+  };
+  connectedCb = ccb;
+  disconnectedCb = dcb;
+  awaitConnection();
 
-    devConn.onAdbMessage(msg => adbOnMessage(msg));
+  devConn.onAdbMessage(msg => adbOnMessage(msg));
 
-    adbOpenConnection();
+  adbOpenConnection();
 }
diff --git a/host/frontend/webrtc_operator/assets/js/app.js b/host/frontend/webrtc_operator/assets/js/app.js
index 53d0f79..4a5a1c9 100644
--- a/host/frontend/webrtc_operator/assets/js/app.js
+++ b/host/frontend/webrtc_operator/assets/js/app.js
@@ -16,450 +16,740 @@
 
 'use strict';
 
-function ConnectToDevice(device_id) {
-  console.log('ConnectToDevice ', device_id);
-  const keyboardCaptureCtrl = document.getElementById('keyboard-capture-control');
-  createToggleControl(keyboardCaptureCtrl, "keyboard", onKeyboardCaptureToggle);
-  const micCaptureCtrl = document.getElementById('mic-capture-control');
-  createToggleControl(micCaptureCtrl, "mic", onMicCaptureToggle);
-  // TODO(b/163867676): Enable the microphone control when the audio stream is
-  // injected into the guest. Until then, control is disabled.
-  micCaptureCtrl.style.display = 'none';
+function showDeviceListUI() {
+  document.getElementById('error-message').style.display = 'none';
+  // Hide the device control screen
+  document.getElementById('device-connection').style.visibility = 'none';
+  // Show the device selection screen
+  document.getElementById('device-selector').style.display = 'visible';
+}
 
-  const deviceScreen = document.getElementById('device-screen');
-  const deviceAudio = document.getElementById('device-audio');
-  const statusMessage = document.getElementById('status-message');
+function showDeviceControlUI() {
+  document.getElementById('error-message').style.display = 'none';
+  // Hide the device selection screen
+  document.getElementById('device-selector').style.display = 'none';
+  // Show the device control screen
+  document.getElementById('device-connection').style.visibility = 'visible';
+}
 
+function websocketUrl(path) {
+  return ((location.protocol == 'http:') ? 'ws:' : 'wss:') + location.host +
+      '/' + path;
+}
+
+async function ConnectDevice(deviceId) {
+  console.debug('Connect: ' + deviceId);
+  // Prepare messages in case of connection failure
   let connectionAttemptDuration = 0;
-  const intervalMs = 500;
-  let deviceStatusEllipsisCount = 0;
-  let animateDeviceStatusMessage = setInterval(function() {
+  const intervalMs = 15000;
+  let connectionInterval = setInterval(() => {
     connectionAttemptDuration += intervalMs;
     if (connectionAttemptDuration > 30000) {
-      statusMessage.className = 'error';
-      statusMessage.textContent = 'Connection should have occurred by now. ' +
-          'Please attempt to restart the guest device.';
-    } else {
-      if (connectionAttemptDuration > 15000) {
-        statusMessage.textContent = 'Connection is taking longer than expected';
-      } else {
-        statusMessage.textContent = 'Connecting to device';
-      }
-      deviceStatusEllipsisCount = (deviceStatusEllipsisCount + 1) % 4;
-      statusMessage.textContent += '.'.repeat(deviceStatusEllipsisCount);
+      showError(
+          'Connection should have occurred by now. ' +
+          'Please attempt to restart the guest device.');
+      clearInterval(connectionInterval);
+    } else if (connectionAttemptDuration > 15000) {
+      showWarning('Connection is taking longer than expected');
     }
   }, intervalMs);
 
-  deviceScreen.addEventListener('loadeddata', (evt) => {
-    clearInterval(animateDeviceStatusMessage);
-    statusMessage.textContent = 'Awaiting bootup and adb connection. Please wait...';
-    resizeDeviceView();
-    deviceScreen.style.visibility = 'visible';
-    // Enable the buttons after the screen is visible.
-    for (const [_, button] of Object.entries(buttons)) {
-      if (!button.adb) {
-        button.button.disabled = false;
-      }
+  let options = {
+    wsUrl: websocketUrl('connect_client'),
+  };
+
+  let module = await import('./cf_webrtc.js');
+  let deviceConnection = await module.Connect(deviceId, options);
+  console.info('Connected to ' + deviceId);
+  clearInterval(connectionInterval);
+  return deviceConnection;
+}
+
+function showWarning(msg) {
+  let element = document.getElementById('error-message');
+  element.className = 'warning';
+  element.textContent = msg;
+  element.style.visibility = 'visible';
+}
+
+function showError(msg) {
+  let element = document.getElementById('error-message');
+  element.className = 'error';
+  element.textContent = msg;
+  element.style.visibility = 'visible';
+}
+
+class DeviceListApp {
+  #websocketUrl;
+  #selectDeviceCb;
+
+  constructor({websocketUrl, selectDeviceCb}) {
+    this.#websocketUrl = websocketUrl;
+    this.#selectDeviceCb = selectDeviceCb;
+  }
+
+  start() {
+    // Get any devices that are already connected
+    this.#UpdateDeviceList();
+
+    // Update the list at the user's request
+    document.getElementById('refresh-list')
+        .addEventListener('click', evt => this.#UpdateDeviceList());
+  }
+
+  #UpdateDeviceList() {
+    let ws = new WebSocket(this.#websocketUrl);
+    ws.onopen = () => {
+      ws.send('give me those device ids');
+    };
+    ws.onmessage = msg => {
+      let device_ids = JSON.parse(msg.data);
+      this.#ShowNewDeviceList(device_ids);
+    };
+  }
+
+  #ShowNewDeviceList(device_ids) {
+    let ul = document.getElementById('device-list');
+    ul.innerHTML = '';
+    let count = 1;
+    let device_to_button_map = {};
+    for (const dev_id of device_ids) {
+      const button_id = 'connect_' + count++;
+      ul.innerHTML +=
+          ('<li class="device_entry" title="Connect to ' + dev_id +
+           '"><div><span>' + dev_id + '</span><button id="' + button_id +
+           '" >Connect</button></div></li>');
+      device_to_button_map[dev_id] = button_id;
     }
-    // Start the adb connection if it is not already started.
-    initializeAdb();
-  });
 
-  let videoStream;
-  let display_label;
-  let buttons = {};
-  let mouseIsDown = false;
-  let deviceConnection;
-  let touchIdSlotMap = new Map();
-  let touchSlots = new Array();
+    for (const [dev_id, button_id] of Object.entries(device_to_button_map)) {
+      let button = document.getElementById(button_id);
+      button.addEventListener('click', evt => {
+        let button = $(evt.target);
+        let div = button.parent();
+        button.remove();
+        div.append('<span class="spinner material-icons">sync</span>');
+        this.#selectDeviceCb(dev_id);
+      });
+    }
+  }
+}  // DeviceListApp
 
-  let bootCompleted = false;
-  let adbConnected = false;
-  function showBootCompletion() {
-    // Screen changed messages are not reported until after boot has completed.
-    // Certain default adb buttons change screen state, so wait for boot
-    // completion before enabling these buttons.
-    if (adbConnected && bootCompleted) {
-      statusMessage.className = 'connected';
-      statusMessage.textContent =
-          'bootup and adb connection established successfully.';
-      setTimeout(function() {
-        statusMessage.style.visibility = 'hidden';
-      }, 5000);
-      for (const [_, button] of Object.entries(buttons)) {
-        if (button.adb) {
-          button.button.disabled = false;
+class DeviceDetailsUpdater {
+  #element;
+
+  constructor() {
+    this.#element = document.getElementById('device-details-hardware');
+  }
+
+  setHardwareDetailsText(text) {
+    this.#element.dataset.hardwareDetailsText = text;
+    return this;
+  }
+
+  setDeviceStateDetailsText(text) {
+    this.#element.dataset.deviceStateDetailsText = text;
+    return this;
+  }
+
+  setDisplayDetailsText(text) {
+    this.#element.dataset.displayDetailsText = text;
+    return this;
+  }
+
+  update() {
+    this.#element.textContent =
+        [
+          this.#element.dataset.hardwareDetailsText,
+          this.#element.dataset.deviceStateDetailsText,
+          this.#element.dataset.displayDetailsText,
+        ].filter(e => e /*remove empty*/)
+            .join('\n');
+  }
+}  // DeviceDetailsUpdater
+
+class DeviceControlApp {
+  #deviceConnection = {};
+  #currentRotation = 0;
+  #displayDescriptions = [];
+  #buttons = {};
+
+  constructor(deviceConnection) {
+    this.#deviceConnection = deviceConnection;
+  }
+
+  start() {
+    console.debug('Device description: ', this.#deviceConnection.description);
+    this.#deviceConnection.onControlMessage(msg => this.#onControlMessage(msg));
+    let keyboardCaptureCtrl = createToggleControl(
+        document.getElementById('keyboard-capture-control'), 'keyboard');
+    let micCaptureCtrl = createToggleControl(
+        document.getElementById('mic-capture-control'), 'mic');
+
+    keyboardCaptureCtrl.OnClick(
+        enabled => this.#onKeyboardCaptureToggle(enabled));
+    micCaptureCtrl.OnClick(enabled => this.#onMicCaptureToggle(enabled));
+
+    this.#showDeviceUI();
+  }
+
+  #showDeviceUI() {
+    window.onresize = evt => this.#resizeDeviceDisplays();
+    // Set up control panel buttons
+    this.#buttons = {};
+    this.#buttons['power'] = createControlPanelButton(
+        'power', 'Power', 'power_settings_new',
+        evt => this.#onControlPanelButton(evt));
+    this.#buttons['home'] = createControlPanelButton(
+        'home', 'Home', 'home', evt => this.#onControlPanelButton(evt));
+    this.#buttons['menu'] = createControlPanelButton(
+        'menu', 'Menu', 'menu', evt => this.#onControlPanelButton(evt));
+    this.#buttons['rotate'] = createControlPanelButton(
+        'rotate', 'Rotate', 'screen_rotation',
+        evt => this.#onRotateButton(evt));
+    this.#buttons['rotate'].adb = true;
+    this.#buttons['volumemute'] = createControlPanelButton(
+        'volumemute', 'Volume Mute', 'volume_mute',
+        evt => this.#onControlPanelButton(evt));
+    this.#buttons['volumedown'] = createControlPanelButton(
+        'volumedown', 'Volume Down', 'volume_down',
+        evt => this.#onControlPanelButton(evt));
+    this.#buttons['volumeup'] = createControlPanelButton(
+        'volumeup', 'Volume Up', 'volume_up',
+        evt => this.#onControlPanelButton(evt));
+    createModalButton(
+        'device-details-button', 'device-details-modal',
+        'device-details-close');
+    createModalButton(
+        'bluetooth-console-button', 'bluetooth-console-modal',
+        'bluetooth-console-close');
+
+    if (this.#deviceConnection.description.custom_control_panel_buttons.length >
+        0) {
+      document.getElementById('control-panel-custom-buttons').style.display =
+          'flex';
+      for (const button of this.#deviceConnection.description
+               .custom_control_panel_buttons) {
+        if (button.shell_command) {
+          // This button's command is handled by sending an ADB shell command.
+          this.#buttons[button.command] = createControlPanelButton(
+              button.command, button.title, button.icon_name,
+              e => this.#onCustomShellButton(button.shell_command, e),
+              'control-panel-custom-buttons');
+          this.#buttons[button.command].adb = true;
+        } else if (button.device_states) {
+          // This button corresponds to variable hardware device state(s).
+          this.#buttons[button.command] = createControlPanelButton(
+              button.command, button.title, button.icon_name,
+              this.#getCustomDeviceStateButtonCb(button.device_states),
+              'control-panel-custom-buttons');
+          for (const device_state of button.device_states) {
+            // hinge_angle is currently injected via an adb shell command that
+            // triggers a guest binary.
+            if ('hinge_angle_value' in device_state) {
+              this.#buttons[button.command].adb = true;
+            }
+          }
+        } else {
+          // This button's command is handled by custom action server.
+          this.#buttons[button.command] = createControlPanelButton(
+              button.command, button.title, button.icon_name,
+              evt => this.#onControlPanelButton(evt),
+              'control-panel-custom-buttons');
         }
       }
     }
+
+    // Set up displays
+    this.#createDeviceDisplays();
+
+    // Set up audio
+    const deviceAudio = document.getElementById('device-audio');
+    for (const audio_desc of this.#deviceConnection.description.audio_streams) {
+      let stream_id = audio_desc.stream_id;
+      this.#deviceConnection.getStream(stream_id)
+          .then(stream => {
+            deviceAudio.srcObject = stream;
+          })
+          .catch(e => console.error('Unable to get audio stream: ', e));
+    }
+
+    // Set up touch input
+    this.#startMouseTracking();
+
+    this.#updateDeviceHardwareDetails(
+        this.#deviceConnection.description.hardware);
+    this.#updateDeviceDisplayDetails(
+        this.#deviceConnection.description.displays[0]);
+
+    // Show the error message and disable buttons when the WebRTC connection
+    // fails.
+    this.#deviceConnection.onConnectionStateChange(state => {
+      if (state == 'disconnected' || state == 'failed') {
+        this.#showWebrtcError();
+      }
+    });
+
+    let bluetoothConsole =
+        cmdConsole('bluetooth-console-view', 'bluetooth-console-input');
+    bluetoothConsole.addCommandListener(cmd => {
+      let inputArr = cmd.split(' ');
+      let command = inputArr[0];
+      inputArr.shift();
+      let args = inputArr;
+      this.#deviceConnection.sendBluetoothMessage(
+          createRootcanalMessage(command, args));
+    });
+    this.#deviceConnection.onBluetoothMessage(msg => {
+      bluetoothConsole.addLine(decodeRootcanalMessage(msg));
+    });
   }
 
-  function initializeAdb() {
-    init_adb(
-        deviceConnection,
-        function() {
-          adbConnected = true;
-          showBootCompletion();
-        },
-        function() {
-          statusMessage.className = 'error';
-          statusMessage.textContent = 'adb connection failed.';
-          statusMessage.style.visibility = 'visible';
-          for (const [_, button] of Object.entries(buttons)) {
-            if (button.adb) {
-              button.button.disabled = true;
-            }
-          }
-        });
+  #showWebrtcError() {
+    document.getElementById('status-message').className = 'error';
+    document.getElementById('status-message').textContent =
+        'No connection to the guest device. ' +
+        'Please ensure the WebRTC process on the host machine is active.';
+    document.getElementById('status-message').style.visibility = 'visible';
+    const deviceDisplays = document.getElementById('device-displays');
+    deviceDisplays.style.display = 'none';
+    for (const [_, button] of Object.entries(this.#buttons)) {
+      button.disabled = true;
+    }
   }
 
-  let currentRotation = 0;
-  let currentDisplayDetails;
-  function onControlMessage(message) {
+  #takePhoto() {
+    const imageCapture = this.#deviceConnection.imageCapture;
+    if (imageCapture) {
+      const photoSettings = {
+        imageWidth: this.#deviceConnection.cameraWidth,
+        imageHeight: this.#deviceConnection.cameraHeight
+      };
+      imageCapture.takePhoto(photoSettings)
+          .then(blob => blob.arrayBuffer())
+          .then(buffer => this.#deviceConnection.sendOrQueueCameraData(buffer))
+          .catch(error => console.error(error));
+    }
+  }
+
+  #getCustomDeviceStateButtonCb(device_states) {
+    let states = device_states;
+    let index = 0;
+    return e => {
+      if (e.type == 'mousedown') {
+        // Reset any overridden device state.
+        adbShell('cmd device_state state reset');
+        // Send a device_state message for the current state.
+        let message = {
+          command: 'device_state',
+          ...states[index],
+        };
+        this.#deviceConnection.sendControlMessage(JSON.stringify(message));
+        console.debug('Control message sent: ', JSON.stringify(message));
+        let lidSwitchOpen = null;
+        if ('lid_switch_open' in states[index]) {
+          lidSwitchOpen = states[index].lid_switch_open;
+        }
+        let hingeAngle = null;
+        if ('hinge_angle_value' in states[index]) {
+          hingeAngle = states[index].hinge_angle_value;
+          // TODO(b/181157794): Use a custom Sensor HAL for hinge_angle
+          // injection instead of this guest binary.
+          adbShell(
+              '/vendor/bin/cuttlefish_sensor_injection hinge_angle ' +
+              states[index].hinge_angle_value);
+        }
+        // Update the Device Details view.
+        this.#updateDeviceStateDetails(lidSwitchOpen, hingeAngle);
+        // Cycle to the next state.
+        index = (index + 1) % states.length;
+      }
+    }
+  }
+
+  #resizeDeviceDisplays() {
+    const deviceDisplayPadding = 10;
+
+    let deviceDisplayList = document.getElementsByClassName('device-display');
+    let deviceDisplayVideoList =
+        document.getElementsByClassName('device-display-video');
+
+    const deviceDisplays = document.getElementById('device-displays');
+    const rotationDegrees = this.#getTransformRotation(deviceDisplays);
+    const rotationRadians = rotationDegrees * Math.PI / 180;
+
+    // Auto-scale the screen based on window size.
+    let availableWidth = deviceDisplays.clientWidth;
+    let availableHeight = deviceDisplays.clientHeight;
+
+    // Reserve space for padding between the displays.
+    availableWidth = availableWidth -
+        (this.#displayDescriptions.length * deviceDisplayPadding);
+
+    // Loop once over all of the displays to compute the total space needed.
+    let neededWidth = 0;
+    let neededHeight = 0;
+    for (let i = 0; i < deviceDisplayList.length; i++) {
+      let deviceDisplayDescription = this.#displayDescriptions[i];
+      let deviceDisplayVideo = deviceDisplayVideoList[i];
+
+      const originalDisplayWidth = deviceDisplayDescription.x_res;
+      const originalDisplayHeight = deviceDisplayDescription.y_res;
+
+      const neededBoundingBoxWidth =
+          Math.abs(Math.cos(rotationRadians) * originalDisplayWidth) +
+          Math.abs(Math.sin(rotationRadians) * originalDisplayHeight);
+      const neededBoundingBoxHeight =
+          Math.abs(Math.sin(rotationRadians) * originalDisplayWidth) +
+          Math.abs(Math.cos(rotationRadians) * originalDisplayHeight);
+
+      neededWidth = neededWidth + neededBoundingBoxWidth;
+      neededHeight = Math.max(neededHeight, neededBoundingBoxHeight);
+    }
+
+    const scaling =
+        Math.min(availableWidth / neededWidth, availableHeight / neededHeight);
+
+    // Loop again over all of the displays to set the sizes and positions.
+    let deviceDisplayLeftOffset = 0;
+    for (let i = 0; i < deviceDisplayList.length; i++) {
+      let deviceDisplay = deviceDisplayList[i];
+      let deviceDisplayVideo = deviceDisplayVideoList[i];
+      let deviceDisplayDescription = this.#displayDescriptions[i];
+
+      const originalDisplayWidth = deviceDisplayDescription.x_res;
+      const originalDisplayHeight = deviceDisplayDescription.y_res;
+
+      const scaledDisplayWidth = originalDisplayWidth * scaling;
+      const scaledDisplayHeight = originalDisplayHeight * scaling;
+
+      const neededBoundingBoxWidth =
+          Math.abs(Math.cos(rotationRadians) * originalDisplayWidth) +
+          Math.abs(Math.sin(rotationRadians) * originalDisplayHeight);
+      const neededBoundingBoxHeight =
+          Math.abs(Math.sin(rotationRadians) * originalDisplayWidth) +
+          Math.abs(Math.cos(rotationRadians) * originalDisplayHeight);
+
+      const scaledBoundingBoxWidth = neededBoundingBoxWidth * scaling;
+      const scaledBoundingBoxHeight = neededBoundingBoxHeight * scaling;
+
+      const offsetX = (scaledBoundingBoxWidth - scaledDisplayWidth) / 2;
+      const offsetY = (scaledBoundingBoxHeight - scaledDisplayHeight) / 2;
+
+      deviceDisplayVideo.style.width = scaledDisplayWidth;
+      deviceDisplayVideo.style.height = scaledDisplayHeight;
+      deviceDisplayVideo.style.transform = `translateX(${offsetX}px) ` +
+          `translateY(${offsetY}px) ` +
+          `rotateZ(${rotationDegrees}deg) `;
+
+      deviceDisplay.style.left = `${deviceDisplayLeftOffset}px`;
+      deviceDisplay.style.width = scaledBoundingBoxWidth;
+      deviceDisplay.style.height = scaledBoundingBoxHeight;
+
+      deviceDisplayLeftOffset = deviceDisplayLeftOffset + deviceDisplayPadding +
+          scaledBoundingBoxWidth;
+    }
+  }
+
+  #getTransformRotation(element) {
+    if (!element.style.textIndent) {
+      return 0;
+    }
+    // Remove 'px' and convert to float.
+    return parseFloat(element.style.textIndent.slice(0, -2));
+  }
+
+  #onControlMessage(message) {
     let message_data = JSON.parse(message.data);
-    console.log(message_data)
+    console.debug('Control message received: ', message_data)
     let metadata = message_data.metadata;
     if (message_data.event == 'VIRTUAL_DEVICE_BOOT_STARTED') {
       // Start the adb connection after receiving the BOOT_STARTED message.
       // (This is after the adbd start message. Attempting to connect
       // immediately after adbd starts causes issues.)
-      initializeAdb();
-    }
-    if (message_data.event == 'VIRTUAL_DEVICE_BOOT_COMPLETED') {
-      bootCompleted = true;
-      showBootCompletion();
+      this.#initializeAdb();
     }
     if (message_data.event == 'VIRTUAL_DEVICE_SCREEN_CHANGED') {
-      if (metadata.rotation != currentRotation) {
+      if (metadata.rotation != this.#currentRotation) {
         // Animate the screen rotation.
-        deviceScreen.style.transition = 'transform 1s';
-      } else {
-        // Don't animate screen resizes, since these appear as odd sliding
-        // animations if the screen is rotated due to the translateY.
-        deviceScreen.style.transition = '';
+        const targetRotation = metadata.rotation == 0 ? 0 : -90;
+
+        $('#device-displays')
+            .animate(
+                {
+                  textIndent: targetRotation,
+                },
+                {
+                  duration: 1000,
+                  step: (now, tween) => {
+                    this.#resizeDeviceDisplays();
+                  },
+                });
       }
 
-      currentRotation = metadata.rotation;
-      updateDeviceDisplayDetails({
-        dpi: metadata.dpi,
-        x_res: metadata.width,
-        y_res: metadata.height
-      });
-
-      resizeDeviceView();
+      this.#currentRotation = metadata.rotation;
+      this.#updateDeviceDisplayDetails(
+          {dpi: metadata.dpi, x_res: metadata.width, y_res: metadata.height});
     }
-  }
-
-  const screensDiv = document.getElementById('screens');
-  function resizeDeviceView() {
-    // Auto-scale the screen based on window size.
-    // Max window width of 70%, allowing space for the control panel.
-    let ww = screensDiv.offsetWidth * 0.7;
-    let wh = screensDiv.offsetHeight;
-    let vw = currentDisplayDetails.x_res;
-    let vh = currentDisplayDetails.y_res;
-    let scaling = vw * wh > vh * ww ? ww / vw : wh / vh;
-    if (currentRotation == 0) {
-      deviceScreen.style.transform = null;
-      deviceScreen.style.width = vw * scaling;
-      deviceScreen.style.height = vh * scaling;
-    } else if (currentRotation == 1) {
-      deviceScreen.style.transform =
-          `rotateZ(-90deg) translateY(-${vh * scaling}px)`;
-      // When rotated, w and h are swapped.
-      deviceScreen.style.width = vh * scaling;
-      deviceScreen.style.height = vw * scaling;
-    }
-  }
-  window.onresize = resizeDeviceView;
-
-  function createControlPanelButton(command, title, icon_name,
-      listener=onControlPanelButton,
-      parent_id='control-panel-default-buttons') {
-    let button = document.createElement('button');
-    document.getElementById(parent_id).appendChild(button);
-    button.title = title;
-    button.dataset.command = command;
-    button.disabled = true;
-    // Capture mousedown/up/out commands instead of click to enable
-    // hold detection. mouseout is used to catch if the user moves the
-    // mouse outside the button while holding down.
-    button.addEventListener('mousedown', listener);
-    button.addEventListener('mouseup', listener);
-    button.addEventListener('mouseout', listener);
-    // Set the button image using Material Design icons.
-    // See http://google.github.io/material-design-icons
-    // and https://material.io/resources/icons
-    button.classList.add('material-icons');
-    button.innerHTML = icon_name;
-    buttons[command] = { 'button': button }
-    return buttons[command];
-  }
-  createControlPanelButton('power', 'Power', 'power_settings_new');
-  createControlPanelButton('home', 'Home', 'home');
-  createControlPanelButton('menu', 'Menu', 'menu');
-  createControlPanelButton('rotate', 'Rotate', 'screen_rotation', onRotateButton);
-  buttons['rotate'].adb = true;
-  createControlPanelButton('volumemute', 'Volume Mute', 'volume_mute');
-  createControlPanelButton('volumedown', 'Volume Down', 'volume_down');
-  createControlPanelButton('volumeup', 'Volume Up', 'volume_up');
-
-  const deviceDetailsModal = document.getElementById('device-details-modal');
-  const deviceDetailsButton = document.getElementById('device-details-button');
-  const deviceDetailsClose = document.getElementById('device-details-close');
-  function showHideDeviceDetailsModal(show) {
-    // Position the modal to the right of the device details button.
-    deviceDetailsModal.style.top = deviceDetailsButton.offsetTop;
-    deviceDetailsModal.style.left = deviceDetailsButton.offsetWidth + 30;
-    if (show) {
-      deviceDetailsModal.style.display = 'block';
-    } else {
-      deviceDetailsModal.style.display = 'none';
-    }
-  }
-  // Allow the device details button to toggle the modal,
-  deviceDetailsButton.addEventListener('click',
-      evt => showHideDeviceDetailsModal(deviceDetailsModal.style.display != 'block'));
-  // but the close button always closes.
-  deviceDetailsClose.addEventListener('click',
-      evt => showHideDeviceDetailsModal(false));
-
-  let options = {
-    wsUrl: ((location.protocol == 'http:') ? 'ws://' : 'wss://') +
-      location.host + '/connect_client',
-  };
-
-  function showWebrtcError() {
-    statusMessage.className = 'error';
-    statusMessage.textContent = 'No connection to the guest device. ' +
-        'Please ensure the WebRTC process on the host machine is active.';
-    statusMessage.style.visibility = 'visible';
-    deviceScreen.style.display = 'none';
-    for (const [_, button] of Object.entries(buttons)) {
-      button.button.disabled = true;
-    }
-  }
-
-  import('./cf_webrtc.js')
-    .then(webrtcModule => webrtcModule.Connect(device_id, options))
-    .then(devConn => {
-      deviceConnection = devConn;
-      // TODO(b/143667633): get multiple display configuration from the
-      // description object
-      console.log(deviceConnection.description);
-      let stream_id = devConn.description.displays[0].stream_id;
-      devConn.getStream(stream_id).then(stream => {
-        videoStream = stream;
-        display_label = stream_id;
-        deviceScreen.srcObject = videoStream;
-      }).catch(e => console.error('Unable to get display stream: ', e));
-      for (const audio_desc of devConn.description.audio_streams) {
-        let stream_id = audio_desc.stream_id;
-        devConn.getStream(stream_id).then(stream => {
-          deviceAudio.srcObject = stream;
-        }).catch(e => console.error('Unable to get audio stream: ', e));
+    if (message_data.event == 'VIRTUAL_DEVICE_CAPTURE_IMAGE') {
+      if (this.$deviceConnection.cameraEnabled) {
+        this.#takePhoto();
       }
-      startMouseTracking();  // TODO stopMouseTracking() when disconnected
-      updateDeviceHardwareDetails(deviceConnection.description.hardware);
-      updateDeviceDisplayDetails(deviceConnection.description.displays[0]);
-      if (deviceConnection.description.custom_control_panel_buttons.length > 0) {
-        document.getElementById('control-panel-custom-buttons').style.display = 'flex';
-        for (const button of deviceConnection.description.custom_control_panel_buttons) {
-          if (button.shell_command) {
-            // This button's command is handled by sending an ADB shell command.
-            createControlPanelButton(button.command, button.title, button.icon_name,
-                e => onCustomShellButton(button.shell_command, e),
-                'control-panel-custom-buttons');
-            buttons[button.command].adb = true;
-          } else {
-            // This button's command is handled by custom action server.
-            createControlPanelButton(button.command, button.title, button.icon_name,
-                onControlPanelButton,
-                'control-panel-custom-buttons');
-          }
-        }
-      }
-      deviceConnection.onControlMessage(msg => onControlMessage(msg));
-      // Start the screen as hidden. Only show when data is ready.
-      deviceScreen.style.visibility = 'hidden';
-      // Send an initial home button press when WebRTC connects. This is needed
-      // so that the device screen receives an initial frame even if WebRTC is
-      // connected long after the device boots up.
-      deviceConnection.sendControlMessage(JSON.stringify({
-        command: 'home',
-        state: 'down',
-      }));
-      deviceConnection.sendControlMessage(JSON.stringify({
-        command: 'home',
-        state: 'up',
-      }));
-      // Show the error message and disable buttons when the WebRTC connection fails.
-      deviceConnection.onConnectionStateChange(state => {
-        if (state == 'disconnected' || state == 'failed') {
-          showWebrtcError();
-        }
-      });
-  }, rejection => {
-      console.error('Unable to connect: ', rejection);
-      showWebrtcError();
-  });
-
-  let hardwareDetailsText = '';
-  let displayDetailsText = '';
-  function updateDeviceDetailsText() {
-    document.getElementById('device-details-hardware').textContent = [
-      hardwareDetailsText,
-      displayDetailsText,
-    ].join('\n');
+    }
   }
-  function updateDeviceHardwareDetails(hardware) {
+
+  #updateDeviceStateDetails(lidSwitchOpen, hingeAngle) {
+    let deviceStateDetailsTextLines = [];
+    if (lidSwitchOpen != null) {
+      let state = lidSwitchOpen ? 'Opened' : 'Closed';
+      deviceStateDetailsTextLines.push(`Lid Switch - ${state}`);
+    }
+    if (hingeAngle != null) {
+      deviceStateDetailsTextLines.push(`Hinge Angle - ${hingeAngle}`);
+    }
+    let deviceStateDetailsText = deviceStateDetailsTextLines.join('\n');
+    new DeviceDetailsUpdater()
+        .setDeviceStateDetailsText(deviceStateDetailsText)
+        .update();
+  }
+
+  #updateDeviceHardwareDetails(hardware) {
     let hardwareDetailsTextLines = [];
-    Object.keys(hardware).forEach(function(key) {
+    Object.keys(hardware).forEach((key) => {
       let value = hardware[key];
       hardwareDetailsTextLines.push(`${key} - ${value}`);
     });
 
-    hardwareDetailsText = hardwareDetailsTextLines.join('\n');
-    updateDeviceDetailsText();
+    let hardwareDetailsText = hardwareDetailsTextLines.join('\n');
+    new DeviceDetailsUpdater()
+        .setHardwareDetailsText(hardwareDetailsText)
+        .update();
   }
-  function updateDeviceDisplayDetails(display) {
-    currentDisplayDetails = display;
+
+  #updateDeviceDisplayDetails(display) {
     let dpi = display.dpi;
     let x_res = display.x_res;
     let y_res = display.y_res;
-    let rotated = currentRotation == 1 ? ' (Rotated)' : '';
-    displayDetailsText = `Display - ${x_res}x${y_res} (${dpi}DPI)${rotated}`;
-    updateDeviceDetailsText();
+    let rotated = this.#currentRotation == 1 ? ' (Rotated)' : '';
+    let displayDetailsText =
+        `Display - ${x_res}x${y_res} (${dpi}DPI)${rotated}`;
+    new DeviceDetailsUpdater()
+        .setDisplayDetailsText(displayDetailsText)
+        .update();
   }
 
-  function onKeyboardCaptureToggle(enabled) {
-    if (enabled) {
-      startKeyboardTracking();
-    } else {
-      stopKeyboardTracking();
+  // Creates a <video> element and a <div> container element for each display.
+  // The extra <div> container elements are used to maintain the width and
+  // height of the device as the CSS 'transform' property used on the <video>
+  // element for rotating the device only affects the visuals of the element
+  // and not its layout.
+  #createDeviceDisplays() {
+    console.debug(
+        'Display descriptions: ', this.#deviceConnection.description.displays);
+    this.#displayDescriptions = this.#deviceConnection.description.displays;
+    let anyDisplayLoaded = false;
+    const deviceDisplays = document.getElementById('device-displays');
+    for (const deviceDisplayDescription of this.#displayDescriptions) {
+      let deviceDisplay = document.createElement('div');
+      deviceDisplay.classList.add('device-display');
+      // Start the screen as hidden. Only show when data is ready.
+      deviceDisplay.style.visibility = 'hidden';
+
+      let deviceDisplayVideo = document.createElement('video');
+      deviceDisplayVideo.autoplay = true;
+      deviceDisplayVideo.id = deviceDisplayDescription.stream_id;
+      deviceDisplayVideo.classList.add('device-display-video');
+
+      deviceDisplayVideo.addEventListener('loadeddata', (evt) => {
+        if (!anyDisplayLoaded) {
+          anyDisplayLoaded = true;
+          this.#onDeviceDisplayLoaded();
+        }
+      });
+
+      deviceDisplay.appendChild(deviceDisplayVideo);
+      deviceDisplays.appendChild(deviceDisplay);
+
+      let stream_id = deviceDisplayDescription.stream_id;
+      this.#deviceConnection.getStream(stream_id)
+          .then(stream => {
+            deviceDisplayVideo.srcObject = stream;
+          })
+          .catch(e => console.error('Unable to get display stream: ', e));
     }
   }
 
-  function onMicCaptureToggle(enabled) {
-    deviceConnection.useMic(enabled);
+  #initializeAdb() {
+    init_adb(
+        this.#deviceConnection, () => this.#showAdbConnected(),
+        () => this.#showAdbError());
   }
 
-  function onControlPanelButton(e) {
+  #showAdbConnected() {
+    // Screen changed messages are not reported until after boot has completed.
+    // Certain default adb buttons change screen state, so wait for boot
+    // completion before enabling these buttons.
+    document.getElementById('status-message').className = 'connected';
+    document.getElementById('status-message').textContent =
+        'adb connection established successfully.';
+    setTimeout(() => {
+      document.getElementById('status-message').style.visibility = 'hidden';
+    }, 5000);
+    for (const [_, button] of Object.entries(this.#buttons)) {
+      if (button.adb) {
+        button.disabled = false;
+      }
+    }
+  }
+
+  #showAdbError() {
+    document.getElementById('status-message').className = 'error';
+    document.getElementById('status-message').textContent =
+        'adb connection failed.';
+    document.getElementById('status-message').style.visibility = 'visible';
+    for (const [_, button] of Object.entries(this.#buttons)) {
+      if (button.adb) {
+        button.disabled = true;
+      }
+    }
+  }
+
+  #onDeviceDisplayLoaded() {
+    document.getElementById('status-message').textContent =
+        'Awaiting bootup and adb connection. Please wait...';
+    this.#resizeDeviceDisplays();
+
+    let deviceDisplayList = document.getElementsByClassName('device-display');
+    for (const deviceDisplay of deviceDisplayList) {
+      deviceDisplay.style.visibility = 'visible';
+    }
+
+    // Enable the buttons after the screen is visible.
+    for (const [key, button] of Object.entries(this.#buttons)) {
+      if (!button.adb) {
+        button.disabled = false;
+      }
+    }
+    // Start the adb connection if it is not already started.
+    this.#initializeAdb();
+  }
+
+  #onRotateButton(e) {
+    // Attempt to init adb again, in case the initial connection failed.
+    // This succeeds immediately if already connected.
+    this.#initializeAdb();
+    if (e.type == 'mousedown') {
+      adbShell(
+          '/vendor/bin/cuttlefish_sensor_injection rotate ' +
+          (this.#currentRotation == 0 ? 'landscape' : 'portrait'))
+    }
+  }
+
+  #onControlPanelButton(e) {
     if (e.type == 'mouseout' && e.which == 0) {
       // Ignore mouseout events if no mouse button is pressed.
       return;
     }
-    deviceConnection.sendControlMessage(JSON.stringify({
+    this.#deviceConnection.sendControlMessage(JSON.stringify({
       command: e.target.dataset.command,
-      state: e.type == 'mousedown' ? "down" : "up",
+      button_state: e.type == 'mousedown' ? 'down' : 'up',
     }));
   }
 
-  function onRotateButton(e) {
-    // Attempt to init adb again, in case the initial connection failed.
-    // This succeeds immediately if already connected.
-    initializeAdb();
-    if (e.type == 'mousedown') {
-      adbShell(
-          '/vendor/bin/cuttlefish_rotate ' +
-          (currentRotation == 0 ? 'landscape' : 'portrait'))
-    }
-  }
-  function onCustomShellButton(shell_command, e) {
-    // Attempt to init adb again, in case the initial connection failed.
-    // This succeeds immediately if already connected.
-    initializeAdb();
-    if (e.type == 'mousedown') {
-      adbShell(shell_command);
+  #onKeyboardCaptureToggle(enabled) {
+    if (enabled) {
+      document.addEventListener('keydown', evt => this.#onKeyEvent(evt));
+      document.addEventListener('keyup', evt => this.#onKeyEvent(evt));
+    } else {
+      document.removeEventListener('keydown', evt => this.#onKeyEvent(evt));
+      document.removeEventListener('keyup', evt => this.#onKeyEvent(evt));
     }
   }
 
-  function startMouseTracking() {
+  #onKeyEvent(e) {
+    e.preventDefault();
+    this.#deviceConnection.sendKeyEvent(e.code, e.type);
+  }
+
+  #startMouseTracking() {
+    let $this = this;
+    let mouseIsDown = false;
+    let mouseCtx = {
+      down: false,
+      touchIdSlotMap: new Map(),
+      touchSlots: [],
+    };
+    function onStartDrag(e) {
+      e.preventDefault();
+
+      // console.debug("mousedown at " + e.pageX + " / " + e.pageY);
+      mouseCtx.down = true;
+
+      $this.#sendEventUpdate(mouseCtx, e);
+    }
+
+    function onEndDrag(e) {
+      e.preventDefault();
+
+      // console.debug("mouseup at " + e.pageX + " / " + e.pageY);
+      mouseCtx.down = false;
+
+      $this.#sendEventUpdate(mouseCtx, e);
+    }
+
+    function onContinueDrag(e) {
+      e.preventDefault();
+
+      // console.debug("mousemove at " + e.pageX + " / " + e.pageY + ", down=" +
+      // mouseIsDown);
+      if (mouseCtx.down) {
+        $this.#sendEventUpdate(mouseCtx, e);
+      }
+    }
+
+    let deviceDisplayList = document.getElementsByClassName('device-display');
     if (window.PointerEvent) {
-      deviceScreen.addEventListener('pointerdown', onStartDrag);
-      deviceScreen.addEventListener('pointermove', onContinueDrag);
-      deviceScreen.addEventListener('pointerup', onEndDrag);
+      for (const deviceDisplay of deviceDisplayList) {
+        deviceDisplay.addEventListener('pointerdown', onStartDrag);
+        deviceDisplay.addEventListener('pointermove', onContinueDrag);
+        deviceDisplay.addEventListener('pointerup', onEndDrag);
+      }
     } else if (window.TouchEvent) {
-      deviceScreen.addEventListener('touchstart', onStartDrag);
-      deviceScreen.addEventListener('touchmove', onContinueDrag);
-      deviceScreen.addEventListener('touchend', onEndDrag);
+      for (const deviceDisplay of deviceDisplayList) {
+        deviceDisplay.addEventListener('touchstart', onStartDrag);
+        deviceDisplay.addEventListener('touchmove', onContinueDrag);
+        deviceDisplay.addEventListener('touchend', onEndDrag);
+      }
     } else if (window.MouseEvent) {
-      deviceScreen.addEventListener('mousedown', onStartDrag);
-      deviceScreen.addEventListener('mousemove', onContinueDrag);
-      deviceScreen.addEventListener('mouseup', onEndDrag);
+      for (const deviceDisplay of deviceDisplayList) {
+        deviceDisplay.addEventListener('mousedown', onStartDrag);
+        deviceDisplay.addEventListener('mousemove', onContinueDrag);
+        deviceDisplay.addEventListener('mouseup', onEndDrag);
+      }
     }
   }
 
-  function stopMouseTracking() {
-    if (window.PointerEvent) {
-      deviceScreen.removeEventListener('pointerdown', onStartDrag);
-      deviceScreen.removeEventListener('pointermove', onContinueDrag);
-      deviceScreen.removeEventListener('pointerup', onEndDrag);
-    } else if (window.TouchEvent) {
-      deviceScreen.removeEventListener('touchstart', onStartDrag);
-      deviceScreen.removeEventListener('touchmove', onContinueDrag);
-      deviceScreen.removeEventListener('touchend', onEndDrag);
-    } else if (window.MouseEvent) {
-      deviceScreen.removeEventListener('mousedown', onStartDrag);
-      deviceScreen.removeEventListener('mousemove', onContinueDrag);
-      deviceScreen.removeEventListener('mouseup', onEndDrag);
-    }
-  }
+  #sendEventUpdate(ctx, e) {
+    let eventType = e.type.substring(0, 5);
 
-  function startKeyboardTracking() {
-    document.addEventListener('keydown', onKeyEvent);
-    document.addEventListener('keyup', onKeyEvent);
-  }
-
-  function stopKeyboardTracking() {
-    document.removeEventListener('keydown', onKeyEvent);
-    document.removeEventListener('keyup', onKeyEvent);
-  }
-
-  function onStartDrag(e) {
-    e.preventDefault();
-
-    // console.log("mousedown at " + e.pageX + " / " + e.pageY);
-    mouseIsDown = true;
-
-    sendEventUpdate(true, e);
-  }
-
-  function onEndDrag(e) {
-    e.preventDefault();
-
-    // console.log("mouseup at " + e.pageX + " / " + e.pageY);
-    mouseIsDown = false;
-
-    sendEventUpdate(false, e);
-  }
-
-  function onContinueDrag(e) {
-    e.preventDefault();
-
-    // console.log("mousemove at " + e.pageX + " / " + e.pageY + ", down=" +
-    // mouseIsDown);
-    if (mouseIsDown) {
-      sendEventUpdate(true, e);
-    }
-  }
-
-  function sendEventUpdate(down, e) {
-    console.assert(deviceConnection, 'Can\'t send mouse update without device');
-    var eventType = e.type.substring(0, 5);
+    // The <video> element:
+    const deviceDisplay = e.target;
 
     // Before the first video frame arrives there is no way to know width and
     // height of the device's screen, so turn every click into a click at 0x0.
     // A click at that position is not more dangerous than anywhere else since
     // the user is clicking blind anyways.
-    const videoWidth = deviceScreen.videoWidth? deviceScreen.videoWidth: 1;
-    const videoHeight = deviceScreen.videoHeight? deviceScreen.videoHeight: 1;
-    const elementWidth = deviceScreen.offsetWidth? deviceScreen.offsetWidth: 1;
-    const elementHeight = deviceScreen.offsetHeight? deviceScreen.offsetHeight: 1;
+    const videoWidth = deviceDisplay.videoWidth ? deviceDisplay.videoWidth : 1;
+    const videoHeight =
+        deviceDisplay.videoHeight ? deviceDisplay.videoHeight : 1;
+    const elementWidth =
+        deviceDisplay.offsetWidth ? deviceDisplay.offsetWidth : 1;
+    const elementHeight =
+        deviceDisplay.offsetHeight ? deviceDisplay.offsetHeight : 1;
 
     // vh*ew > eh*vw? then scale h instead of w
     const scaleHeight = videoHeight * elementWidth > videoWidth * elementHeight;
-    var elementScaling = 0, videoScaling = 0;
+    let elementScaling = 0, videoScaling = 0;
     if (scaleHeight) {
       elementScaling = elementHeight;
       videoScaling = videoHeight;
@@ -492,35 +782,36 @@
     //         (which equals the video size here).
     //       - The sent ABS_X and ABS_Y values need to be scaled based on the
     //         ratio between the max size (video size) and in-browser size.
-    const scaling = scaleWidth ? videoWidth / elementWidth : videoHeight / elementHeight;
+    const scaling =
+        scaleWidth ? videoWidth / elementWidth : videoHeight / elementHeight;
 
-    var xArr = [];
-    var yArr = [];
-    var idArr = [];
-    var slotArr = [];
+    let xArr = [];
+    let yArr = [];
+    let idArr = [];
+    let slotArr = [];
 
-    if (eventType == "mouse" || eventType == "point") {
+    if (eventType == 'mouse' || eventType == 'point') {
       xArr.push(e.offsetX);
       yArr.push(e.offsetY);
 
       let thisId = -1;
-      if (eventType == "point") {
+      if (eventType == 'point') {
         thisId = e.pointerId;
       }
 
       slotArr.push(0);
       idArr.push(thisId);
-    } else if (eventType == "touch") {
+    } else if (eventType == 'touch') {
       // touchstart: list of touch points that became active
       // touchmove: list of touch points that changed
       // touchend: list of touch points that were removed
       let changes = e.changedTouches;
       let rect = e.target.getBoundingClientRect();
-      for (var i=0; i < changes.length; i++) {
+      for (let i = 0; i < changes.length; i++) {
         xArr.push(changes[i].pageX - rect.left);
         yArr.push(changes[i].pageY - rect.top);
-        if (touchIdSlotMap.has(changes[i].identifier)) {
-          let slot = touchIdSlotMap.get(changes[i].identifier);
+        if (ctx.touchIdSlotMap.has(changes[i].identifier)) {
+          let slot = ctx.touchIdSlotMap.get(changes[i].identifier);
 
           slotArr.push(slot);
           if (e.type == 'touchstart') {
@@ -530,26 +821,26 @@
           } else if (e.type == 'touchmove') {
             idArr.push(changes[i].identifier);
           } else if (e.type == 'touchend') {
-            touchSlots[slot] = false;
-            touchIdSlotMap.delete(changes[i].identifier);
+            ctx.touchSlots[slot] = false;
+            ctx.touchIdSlotMap.delete(changes[i].identifier);
             idArr.push(-1);
           }
         } else {
           if (e.type == 'touchstart') {
             let slot = -1;
-            for (var j=0; j < touchSlots.length; j++) {
-              if (!touchSlots[j]) {
+            for (let j = 0; j < ctx.touchSlots.length; j++) {
+              if (!ctx.touchSlots[j]) {
                 slot = j;
                 break;
               }
             }
             if (slot == -1) {
-              slot = touchSlots.length;
-              touchSlots.push(true);
+              slot = ctx.touchSlots.length;
+              ctx.touchSlots.push(true);
             }
             slotArr.push(slot);
-            touchSlots[slot] = true;
-            touchIdSlotMap.set(changes[i].identifier, slot);
+            ctx.touchSlots[slot] = true;
+            ctx.touchIdSlotMap.set(changes[i].identifier, slot);
             idArr.push(changes[i].identifier);
           } else if (e.type == 'touchmove') {
             // error
@@ -564,11 +855,12 @@
       }
     }
 
-    for (var i=0; i < xArr.length; i++) {
+    for (let i = 0; i < xArr.length; i++) {
       xArr[i] = xArr[i] * scaling;
       yArr[i] = yArr[i] * scaling;
 
-      // Substract the offset produced by the difference in aspect ratio, if any.
+      // Substract the offset produced by the difference in aspect ratio, if
+      // any.
       if (scaleWidth) {
         // Width was scaled, leaving excess content height, so subtract from y.
         yArr[i] -= (elementHeight * scaling - videoHeight) / 2;
@@ -584,62 +876,48 @@
     // NOTE: Rotation is handled automatically because the CSS rotation through
     // transforms also rotates the coordinates of events on the object.
 
-    deviceConnection.sendMultiTouch(
-    {idArr, xArr, yArr, down, slotArr, display_label});
+    const display_label = deviceDisplay.id;
+
+    this.#deviceConnection.sendMultiTouch(
+        {idArr, xArr, yArr, down: ctx.down, slotArr, display_label});
   }
 
-  function onKeyEvent(e) {
-    e.preventDefault();
-    console.assert(deviceConnection, 'Can\'t send key event without device');
-    deviceConnection.sendKeyEvent(e.code, e.type);
-  }
-}
-
-/******************************************************************************/
-
-function ConnectDeviceCb(dev_id) {
-  console.log('Connect: ' + dev_id);
-  // Hide the device selection screen
-  document.getElementById('device-selector').style.display = 'none';
-  // Show the device control screen
-  document.getElementById('device-connection').style.visibility = 'visible';
-  ConnectToDevice(dev_id);
-}
-
-function ShowNewDeviceList(device_ids) {
-  let ul = document.getElementById('device-list');
-  ul.innerHTML = "";
-  let count = 1;
-  let device_to_button_map = {};
-  for (const dev_id of device_ids) {
-    const button_id = 'connect_' + count++;
-    ul.innerHTML += ('<li class="device_entry" title="Connect to ' + dev_id
-                     + '">' + dev_id + '<button id="' + button_id
-                     + '" >Connect</button></li>');
-    device_to_button_map[dev_id] = button_id;
+  #onMicCaptureToggle(enabled) {
+    this.#deviceConnection.useMic(enabled);
   }
 
-  for (const [dev_id, button_id] of Object.entries(device_to_button_map)) {
-    document.getElementById(button_id).addEventListener(
-        'click', evt => ConnectDeviceCb(dev_id));
+  #onVideoCaptureToggle(enabled) {
+    this.#deviceConnection.useVideo(enabled);
   }
-}
 
-function UpdateDeviceList() {
-  let url = ((location.protocol == 'http:') ? 'ws:' : 'wss:') + location.host +
-    '/list_devices';
-  let ws = new WebSocket(url);
-  ws.onopen = () => {
-    ws.send("give me those device ids");
-  };
-  ws.onmessage = msg => {
-   let device_ids = JSON.parse(msg.data);
-    ShowNewDeviceList(device_ids);
-  };
-}
+  #onCustomShellButton(shell_command, e) {
+    // Attempt to init adb again, in case the initial connection failed.
+    // This succeeds immediately if already connected.
+    this.#initializeAdb();
+    if (e.type == 'mousedown') {
+      adbShell(shell_command);
+    }
+  }
+}  // DeviceControlApp
 
-// Get any devices that are already connected
-UpdateDeviceList();
-// Update the list at the user's request
-document.getElementById('refresh-list')
-    .addEventListener('click', evt => UpdateDeviceList());
+
+// The app starts by showing the device list
+showDeviceListUI();
+let listDevicesUrl = websocketUrl('list_devices');
+let selectDeviceCb = deviceId => {
+  ConnectDevice(deviceId).then(
+      deviceConnection => {
+        let deviceControlApp = new DeviceControlApp(deviceConnection);
+        deviceControlApp.start();
+        showDeviceControlUI();
+      },
+      err => {
+        console.error('Unable to connect: ', err);
+        showError(
+            'No connection to the guest device. ' +
+            'Please ensure the WebRTC process on the host machine is active.');
+      });
+};
+let deviceListApp =
+    new DeviceListApp({websocketUrl: listDevicesUrl, selectDeviceCb});
+deviceListApp.start();
diff --git a/host/frontend/webrtc_operator/assets/js/cf_webrtc.js b/host/frontend/webrtc_operator/assets/js/cf_webrtc.js
index ed7fc37..ad045f0 100644
--- a/host/frontend/webrtc_operator/assets/js/cf_webrtc.js
+++ b/host/frontend/webrtc_operator/assets/js/cf_webrtc.js
@@ -15,7 +15,7 @@
  */
 
 function createDataChannel(pc, label, onMessage) {
-  console.log('creating data channel: ' + label);
+  console.debug('creating data channel: ' + label);
   let dataChannel = pc.createDataChannel(label);
   // Return an object with a send function like that of the dataChannel, but
   // that only actually sends over the data channel once it has connected.
@@ -25,11 +25,11 @@
         resolve(dataChannel);
       };
       dataChannel.onclose = () => {
-        console.log(
+        console.debug(
             'Data channel=' + label + ' state=' + dataChannel.readyState);
       };
       dataChannel.onmessage = onMessage ? onMessage : (msg) => {
-        console.log('Data channel=' + label + ' data="' + msg.data + '"');
+        console.debug('Data channel=' + label + ' data="' + msg.data + '"');
       };
       dataChannel.onerror = err => {
         reject(err);
@@ -45,7 +45,7 @@
 }
 
 function awaitDataChannel(pc, label, onMessage) {
-  console.log('expecting data channel: ' + label);
+  console.debug('expecting data channel: ' + label);
   // Return an object with a send function like that of the dataChannel, but
   // that only actually sends over the data channel once it has connected.
   return {
@@ -58,11 +58,11 @@
             resolve(dataChannel);
           };
           dataChannel.onclose = () => {
-            console.log(
+            console.debug(
                 'Data channel=' + label + ' state=' + dataChannel.readyState);
           };
           dataChannel.onmessage = onMessage ? onMessage : (msg) => {
-            console.log('Data channel=' + label + ' data="' + msg.data + '"');
+            console.debug('Data channel=' + label + ' data="' + msg.data + '"');
           };
           dataChannel.onerror = err => {
             reject(err);
@@ -82,12 +82,22 @@
 }
 
 class DeviceConnection {
-  constructor(pc, control, audio_stream) {
+  constructor(pc, control, media_stream) {
     this._pc = pc;
     this._control = control;
-    this._audio_stream = audio_stream;
+    this._media_stream = media_stream;
     // Disable the microphone by default
     this.useMic(false);
+    this.useVideo(false);
+    this._cameraDataChannel = pc.createDataChannel('camera-data-channel');
+    this._cameraDataChannel.binaryType = 'arraybuffer';
+    this._cameraInputQueue = new Array();
+    var self = this;
+    this._cameraDataChannel.onbufferedamountlow = () => {
+      if (self._cameraInputQueue.length > 0) {
+        self.sendCameraData(self._cameraInputQueue.shift());
+      }
+    };
     this._inputChannel = createDataChannel(pc, 'input-channel');
     this._adbChannel = createDataChannel(pc, 'adb-channel', (msg) => {
       if (this._onAdbMessage) {
@@ -103,11 +113,20 @@
         console.error('Received unexpected Control message');
       }
     });
+    this._bluetoothChannel =
+        createDataChannel(pc, 'bluetooth-channel', (msg) => {
+          if (this._onBluetoothMessage) {
+            this._onBluetoothMessage(msg.data);
+          } else {
+            console.error('Received unexpected Bluetooth message');
+          }
+        });
+    this.sendCameraResolution();
     this._streams = {};
     this._streamPromiseResolvers = {};
 
     pc.addEventListener('track', e => {
-      console.log('Got remote stream: ', e);
+      console.debug('Got remote stream: ', e);
       for (const stream of e.streams) {
         this._streams[stream.id] = stream;
         if (this._streamPromiseResolvers[stream.id]) {
@@ -128,6 +147,28 @@
     return this._description;
   }
 
+  get imageCapture() {
+    if (this._media_stream) {
+      const track = this._media_stream.getVideoTracks()[0];
+      return new ImageCapture(track);
+    }
+    return undefined;
+  }
+
+  get cameraWidth() {
+    return this._x_res;
+  }
+
+  get cameraHeight() {
+    return this._y_res;
+  }
+
+  get cameraEnabled() {
+    if (this._media_stream) {
+      return this._media_stream.getVideoTracks().some(track => track.enabled);
+    }
+  }
+
   getStream(stream_id) {
     return new Promise((resolve, reject) => {
       if (this._streams[stream_id]) {
@@ -193,21 +234,73 @@
   }
 
   useMic(in_use) {
-    if (this._audio_stream) {
-      this._audio_stream.getTracks().forEach(track => track.enabled = in_use);
+    if (this._media_stream) {
+      this._media_stream.getAudioTracks().forEach(
+          track => track.enabled = in_use);
     }
   }
 
+  useVideo(in_use) {
+    if (this._media_stream) {
+      this._media_stream.getVideoTracks().forEach(
+          track => track.enabled = in_use);
+    }
+  }
+
+  sendCameraResolution() {
+    if (this._media_stream) {
+      const cameraTracks = this._media_stream.getVideoTracks();
+      if (cameraTracks.length > 0) {
+        const settings = cameraTracks[0].getSettings();
+        this._x_res = settings.width;
+        this._y_res = settings.height;
+        this.sendControlMessage(JSON.stringify({
+          command: 'camera_settings',
+          width: settings.width,
+          height: settings.height,
+          frame_rate: settings.frameRate,
+          facing: settings.facingMode
+        }));
+      }
+    }
+  }
+
+  sendOrQueueCameraData(data) {
+    if (this._cameraDataChannel.bufferedAmount > 0 ||
+        this._cameraInputQueue.length > 0) {
+      this._cameraInputQueue.push(data);
+    } else {
+      this.sendCameraData(data);
+    }
+  }
+
+  sendCameraData(data) {
+    const MAX_SIZE = 65535;
+    const END_MARKER = 'EOF';
+    for (let i = 0; i < data.byteLength; i += MAX_SIZE) {
+      // range is clamped to the valid index range
+      this._cameraDataChannel.send(data.slice(i, i + MAX_SIZE));
+    }
+    this._cameraDataChannel.send(END_MARKER);
+  }
+
   // Provide a callback to receive control-related comms from the device
   onControlMessage(cb) {
     this._onControlMessage = cb;
   }
 
+  sendBluetoothMessage(msg) {
+    this._bluetoothChannel.send(msg);
+  }
+
+  onBluetoothMessage(cb) {
+    this._onBluetoothMessage = cb;
+  }
+
   // Provide a callback to receive connectionstatechange states.
   onConnectionStateChange(cb) {
     this._pc.addEventListener(
-      'connectionstatechange',
-      evt => cb(this._pc.connectionState));
+        'connectionstatechange', evt => cb(this._pc.connectionState));
   }
 }
 
@@ -232,7 +325,7 @@
     this._wsPromise = new Promise((resolve, reject) => {
       let ws = new WebSocket(wsUrl);
       ws.onopen = () => {
-        console.info(`Connected to ${wsUrl}`);
+        console.debug(`Connected to ${wsUrl}`);
         resolve(ws);
       };
       ws.onerror = evt => {
@@ -272,7 +365,8 @@
         break;
       default:
         console.error('Unrecognized message type from server: ', type);
-        this._on_connection_failed('Unrecognized message type from server: ' + type);
+        this._on_connection_failed(
+            'Unrecognized message type from server: ' + type);
         console.error(message);
     }
   }
@@ -334,7 +428,7 @@
   }
 
   ConnectDevice() {
-    console.log('ConnectDevice');
+    console.debug('ConnectDevice');
     this._sendToDevice({type: 'request-offer'});
   }
 
@@ -342,7 +436,7 @@
    * Sends a remote description to the device.
    */
   async sendClientDescription(desc) {
-    console.log('sendClientDescription');
+    console.debug('sendClientDescription');
     this._sendToDevice({type: 'answer', sdp: desc.sdp});
   }
 
@@ -362,15 +456,15 @@
   let pc = new RTCPeerConnection(pc_config);
 
   pc.addEventListener('icecandidate', evt => {
-    console.log('Local ICE Candidate: ', evt.candidate);
+    console.debug('Local ICE Candidate: ', evt.candidate);
   });
   pc.addEventListener('iceconnectionstatechange', evt => {
-    console.log(`ICE State Change: ${pc.iceConnectionState}`);
+    console.debug(`ICE State Change: ${pc.iceConnectionState}`);
   });
   pc.addEventListener(
       'connectionstatechange',
-      evt =>
-          console.log(`WebRTC Connection State Change: ${pc.connectionState}`));
+      evt => console.debug(
+          `WebRTC Connection State Change: ${pc.connectionState}`));
   return pc;
 }
 
@@ -379,8 +473,8 @@
   let requestRet = await control.requestDevice(deviceId);
   let deviceInfo = requestRet.deviceInfo;
   let infraConfig = requestRet.infraConfig;
-  console.log('Device available:');
-  console.log(deviceInfo);
+  console.debug('Device available:');
+  console.debug(deviceInfo);
   let pc_config = {iceServers: []};
   if (infraConfig.ice_servers && infraConfig.ice_servers.length > 0) {
     for (const server of infraConfig.ice_servers) {
@@ -389,27 +483,26 @@
   }
   let pc = createPeerConnection(infraConfig, control);
 
-  let audioStream;
+  let mediaStream;
   try {
-    audioStream =
-        await navigator.mediaDevices.getUserMedia({video: false, audio: true});
-    const audioTracks = audioStream.getAudioTracks();
-    if (audioTracks.length > 0) {
-      console.log(`Using Audio device: ${audioTracks[0].label}, with ${
-        audioTracks.length} tracks`);
-      audioTracks.forEach(track => pc.addTrack(track, audioStream));
-    }
+    mediaStream =
+        await navigator.mediaDevices.getUserMedia({audio: true});
+    const tracks = mediaStream.getTracks();
+    tracks.forEach(track => {
+      console.info(`Using ${track.kind} device: ${track.label}`);
+      pc.addTrack(track, mediaStream);
+    });
   } catch (e) {
-    console.error("Failed to open audio device: ", e);
+    console.error('Failed to open device: ', e);
   }
 
-  let deviceConnection = new DeviceConnection(pc, control, audioStream);
+  let deviceConnection = new DeviceConnection(pc, control, mediaStream);
   deviceConnection.description = deviceInfo;
   async function acceptOfferAndReplyAnswer(offer) {
     try {
       await pc.setRemoteDescription(offer);
       let answer = await pc.createAnswer();
-      console.log('Answer: ', answer);
+      console.debug('Answer: ', answer);
       await pc.setLocalDescription(answer);
       await control.sendClientDescription(answer);
     } catch (e) {
@@ -418,11 +511,11 @@
     }
   }
   control.onOffer(desc => {
-    console.log('Offer: ', desc);
+    console.debug('Offer: ', desc);
     acceptOfferAndReplyAnswer(desc);
   });
   control.onIceCandidate(iceCandidate => {
-    console.log(`Remote ICE Candidate: `, iceCandidate);
+    console.debug(`Remote ICE Candidate: `, iceCandidate);
     pc.addIceCandidate(iceCandidate);
   });
 
diff --git a/host/frontend/webrtc_operator/assets/js/controls.js b/host/frontend/webrtc_operator/assets/js/controls.js
index 31db046..833cc15 100644
--- a/host/frontend/webrtc_operator/assets/js/controls.js
+++ b/host/frontend/webrtc_operator/assets/js/controls.js
@@ -15,24 +15,156 @@
  */
 
 function createToggleControl(elm, iconName, onChangeCb) {
-  let icon = document.createElement("span");
-  icon.classList.add("toggle-control-icon");
-  icon.classList.add("material-icons-outlined");
+  let icon = document.createElement('span');
+  icon.classList.add('toggle-control-icon');
+  icon.classList.add('material-icons-outlined');
   if (iconName) {
     icon.appendChild(document.createTextNode(iconName));
   }
   elm.appendChild(icon);
-  let toggle = document.createElement("label");
-  toggle.classList.add("toggle-control-switch");
-  let input = document.createElement("input");
-  input.type = "checkbox";
+  let toggle = document.createElement('label');
+  toggle.classList.add('toggle-control-switch');
+  let input = document.createElement('input');
+  input.type = 'checkbox';
   toggle.appendChild(input);
-  let slider = document.createElement("span");
-  slider.classList.add("toggle-control-slider");
+  let slider = document.createElement('span');
+  slider.classList.add('toggle-control-slider');
   toggle.appendChild(slider);
-  elm.classList.add("toggle-control");
+  elm.classList.add('toggle-control');
   elm.appendChild(toggle);
-  if (onChangeCb) {
-    input.onchange = e => onChangeCb(e.target.checked);
+  return {
+    // A callback can later be associated with the toggle element by calling
+    // .OnClick(onChangeCb) on the returned object. The callback should accept a
+    // boolean parameter indicating whether the toggle is in ON position.
+    OnClick: cb => input.onchange = e => cb(e.target.checked),
+  };
+}
+
+function createControlPanelButton(
+    command, title, icon_name, listener,
+    parent_id = 'control-panel-default-buttons') {
+  let button = document.createElement('button');
+  document.getElementById(parent_id).appendChild(button);
+  button.title = title;
+  button.dataset.command = command;
+  button.disabled = true;
+  // Capture mousedown/up/out commands instead of click to enable
+  // hold detection. mouseout is used to catch if the user moves the
+  // mouse outside the button while holding down.
+  button.addEventListener('mousedown', listener);
+  button.addEventListener('mouseup', listener);
+  button.addEventListener('mouseout', listener);
+  // Set the button image using Material Design icons.
+  // See http://google.github.io/material-design-icons
+  // and https://material.io/resources/icons
+  button.classList.add('material-icons');
+  button.innerHTML = icon_name;
+  return button;
+}
+
+function createModalButton(button_id, modal_id, close_id) {
+  const modalButton = document.getElementById(button_id);
+  const modalDiv = document.getElementById(modal_id);
+  const modalHeader = modalDiv.querySelector('.modal-header');
+  const modalClose = document.getElementById(close_id);
+
+  // Position the modal to the right of the show modal button.
+  modalDiv.style.top = modalButton.offsetTop;
+  modalDiv.style.left = modalButton.offsetWidth + 30;
+
+  function showHideModal(show) {
+    if (show) {
+      modalButton.classList.add('modal-button-opened')
+      modalDiv.style.display = 'block';
+    } else {
+      modalButton.classList.remove('modal-button-opened')
+      modalDiv.style.display = 'none';
+    }
   }
+  // Allow the show modal button to toggle the modal,
+  modalButton.addEventListener(
+      'click', evt => showHideModal(modalDiv.style.display != 'block'));
+  // but the close button always closes.
+  modalClose.addEventListener('click', evt => showHideModal(false));
+
+  // Allow the modal to be dragged by the header.
+  let modalOffsets = {
+    midDrag: false,
+    mouseDownOffsetX: null,
+    mouseDownOffsetY: null,
+  };
+  modalHeader.addEventListener('mousedown', evt => {
+    modalOffsets.midDrag = true;
+    // Store the offset of the mouse location from the
+    // modal's current location.
+    modalOffsets.mouseDownOffsetX = parseInt(modalDiv.style.left) - evt.clientX;
+    modalOffsets.mouseDownOffsetY = parseInt(modalDiv.style.top) - evt.clientY;
+  });
+  modalHeader.addEventListener('mousemove', evt => {
+    let offsets = modalOffsets;
+    if (offsets.midDrag) {
+      // Move the modal to the mouse location plus the
+      // offset calculated on the initial mouse-down.
+      modalDiv.style.left = evt.clientX + offsets.mouseDownOffsetX;
+      modalDiv.style.top = evt.clientY + offsets.mouseDownOffsetY;
+    }
+  });
+  document.addEventListener('mouseup', evt => {
+    modalOffsets.midDrag = false;
+  });
+}
+
+function cmdConsole(consoleViewName, consoleInputName) {
+  let consoleView = document.getElementById(consoleViewName);
+
+  let addString =
+      function(str) {
+    consoleView.value += str;
+    consoleView.scrollTop = consoleView.scrollHeight;
+  }
+
+  let addLine =
+      function(line) {
+    addString(line + '\r\n');
+  }
+
+  let commandCallbacks = [];
+
+  let addCommandListener =
+      function(f) {
+    commandCallbacks.push(f);
+  }
+
+  let onCommand =
+      function(cmd) {
+    cmd = cmd.trim();
+
+    if (cmd.length == 0) return;
+
+    commandCallbacks.forEach(f => {
+      f(cmd);
+    })
+  }
+
+  addCommandListener(cmd => addLine('>> ' + cmd));
+
+  let consoleInput = document.getElementById(consoleInputName);
+
+  consoleInput.addEventListener('keydown', e => {
+    if ((e.key && e.key == 'Enter') || e.keyCode == 13) {
+      let command = e.target.value;
+
+      e.target.value = '';
+
+      onCommand(command);
+    }
+  });
+
+  return {
+    consoleView: consoleView,
+    consoleInput: consoleInput,
+    addLine: addLine,
+    addString: addString,
+    addCommandListener: addCommandListener,
+  };
 }
diff --git a/host/frontend/webrtc_operator/assets/js/rootcanal.js b/host/frontend/webrtc_operator/assets/js/rootcanal.js
new file mode 100644
index 0000000..e3783c7
--- /dev/null
+++ b/host/frontend/webrtc_operator/assets/js/rootcanal.js
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+function rootCanalCalculateMessageSize(name, args) {
+  let result = 0;
+
+  result += 1 + name.length;  // length of name + it's data
+  result += 1;                // count of args
+
+  for (let i = 0; i < args.length; i++) {
+    result += 1;               // length of args[i]
+    result += args[i].length;  // data of args[i]
+  }
+
+  return result;
+}
+
+function rootCanalAddU8(array, pos, val) {
+  array[pos] = val & 0xff;
+
+  return pos + 1;
+}
+
+function rootCanalAddPayload(array, pos, payload) {
+  array.set(payload, pos);
+
+  return pos + payload.length;
+}
+
+function rootCanalAddString(array, pos, val) {
+  let curPos = pos;
+
+  curPos = rootCanalAddU8(array, curPos, val.length);
+
+  return rootCanalAddPayload(array, curPos, utf8Encoder.encode(val));
+}
+
+function createRootcanalMessage(command, args) {
+  let messageSize = rootCanalCalculateMessageSize(command, args);
+  let arrayBuffer = new ArrayBuffer(messageSize);
+  let array = new Uint8Array(arrayBuffer);
+  let pos = 0;
+
+  pos = rootCanalAddString(array, pos, command);
+  pos = rootCanalAddU8(array, pos, args.length);
+
+  for (let i = 0; i < args.length; i++) {
+    pos = rootCanalAddString(array, pos, args[i]);
+  }
+
+  return array;
+}
+
+function decodeRootcanalMessage(array) {
+  let size = array[0];
+  let message = array.slice(1);
+
+  return utf8Decoder.decode(message);
+}
diff --git a/host/frontend/webrtc_operator/assets/style.css b/host/frontend/webrtc_operator/assets/style.css
index ca4e7b2..61a19b1 100644
--- a/host/frontend/webrtc_operator/assets/style.css
+++ b/host/frontend/webrtc_operator/assets/style.css
@@ -40,12 +40,19 @@
 }
 
 
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+.spinner {
+  animation: spin 1.4s linear infinite;
+}
+
 /* Top header row. */
 
 #header {
   height: 64px;
-  margin-top: 10px;
-  margin-bottom: 10px;
   /* Items inside this use a row Flexbox.*/
   display: flex;
   align-items: center;
@@ -60,9 +67,26 @@
   margin-right: 6px;
 }
 #device-audio {
-  margin-bottom: 5px;
+  height: 44px;
 }
 
+#error-message-div {
+  flex-grow: 1;
+}
+#error-message {
+  color: white;
+  font-family: 'Open Sans', sans-serif;
+  padding: 10px;
+  margin: 10px;
+}
+#error-message.warning {
+  /* dark red */
+  background-color: #927836;
+}
+#error-message.error {
+  /* dark red */
+  background-color: #900000;
+}
 #status-div {
   flex-grow: 1;
 }
@@ -87,23 +111,24 @@
 
 /* Control panel buttons and device screen(s). */
 
-#controls-and-screens {
+#controls-and-displays {
   height: calc(100% - 84px);
 
   /* Items inside this use a row Flexbox.*/
   display: flex;
 }
-#controls-and-screens div {
-  margin-left: 10px;
-  margin-right: 10px;
+
+#controls-and-displays > div {
+  margin-left: 5px;
+  margin-right: 5px;
 }
 
-#device-details-modal {
+.modal {
   /* Start out hidden, and use absolute positioning. */
   display: none;
   position: absolute;
 
-  border-radius: 16px;
+  border-radius: 10px;
   padding: 20px;
   padding-top: 1px;
 
@@ -111,12 +136,13 @@
   color: white;
   font-family: 'Open Sans', sans-serif;
 }
-#device-details-modal-header {
+.modal-header {
+  cursor: move;
   /* Items inside this use a row Flexbox.*/
   display: flex;
   justify-content: space-between;
 }
-#device-details-close {
+.modal-close {
   color: white;
   border: none;
   outline: none;
@@ -125,9 +151,15 @@
 #device-details-modal span {
   white-space: pre;
 }
+#bluetooth-console-input {
+  width: 100%;
+}
+#bluetooth-console-cmd-label {
+  color: white;
+}
 
 .control-panel-column {
-  width: 80px;
+  width: 50px;
   /* Items inside this use a column Flexbox.*/
   display: flex;
   flex-direction: column;
@@ -137,13 +169,13 @@
   /* Give the custom buttons column a blue background. */
   background-color: #1c4587ff;
   height: fit-content;
-  border-radius: 16px;
+  border-radius: 10px;
 }
 
 .control-panel-column button {
-  margin: 10px;
-  height: 60px;
-  font-size: 48px;
+  margin: 0px 0px 5px 0px;
+  height: 50px;
+  font-size: 32px;
 
   color: #e8eaed; /* Google grey 200 */
   border: none;
@@ -153,22 +185,34 @@
 .control-panel-column button:disabled {
   color: #9aa0a6; /* Google grey 500 */
 }
-#device-details-button {
-  margin: 0px;
-  height: 80px;
-  border-radius: 16px;
+.control-panel-column button.modal-button-opened {
+  border-radius: 10px;
   background-color: #5f6368; /* Google grey 700 */
 }
 
-#screens {
+#device-displays {
   /* Take up the remaining width of the window.*/
   flex-grow: 1;
   /* Don't grow taller than the window.*/
   max-height: 100vh;
+  /* Allows child elements to be positioned relative to this element. */
+  position: relative;
 }
 
-#device-screen {
+/*
+ * Container <div> used to wrap each display's <video> element which is used for
+ * maintaining each display's width and height while the display is potentially
+ * rotating.
+ */
+.device-display {
+  /* Prevents #device-displays from using this element when computing flex size. */
+  position: absolute;
+}
+
+/* The actual <video> element for each display. */
+.device-display-video {
+  position: absolute;
+  left:  0px;
   touch-action: none;
-  transform-origin: top right;
   object-fit: cover;
 }
diff --git a/host/frontend/webrtc_operator/certs/create_certs.sh b/host/frontend/webrtc_operator/certs/create_certs.sh
index 9f85e40..fefc275 100755
--- a/host/frontend/webrtc_operator/certs/create_certs.sh
+++ b/host/frontend/webrtc_operator/certs/create_certs.sh
@@ -3,15 +3,15 @@
 # As explained in
 #  https://gist.github.com/darrenjs/4645f115d10aa4b5cebf57483ec82eca
 
-openssl genrsa -des3 -passout pass:x -out server.pass.key 2048
-openssl rsa -passin pass:x -in server.pass.key -out server.key
+openssl genrsa -des3 -passout pass:xxxx -out server.pass.key 2048
+openssl rsa -passin pass:xxxx -in server.pass.key -out server.key
 rm -f server.pass.key
 
 openssl req \
     -subj "/C=US/ST=California/L=Santa Clara/O=Beyond Aggravated/CN=localhost" \
     -new -key server.key -out server.csr
 
-openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
+openssl x509 -req -sha256 -days 99999 -in server.csr -signkey server.key -out server.crt
 rm -f server.csr
 
 # Now create the list of certificates we trust as a client.
diff --git a/host/frontend/webrtc_operator/certs/server.crt b/host/frontend/webrtc_operator/certs/server.crt
index 0b9aa36..f871fcb 100644
--- a/host/frontend/webrtc_operator/certs/server.crt
+++ b/host/frontend/webrtc_operator/certs/server.crt
@@ -1,20 +1,20 @@
 -----BEGIN CERTIFICATE-----
-MIIDVzCCAj8CFBXBydw0e/7l31d9fzO7vrBxAw4yMA0GCSqGSIb3DQEBCwUAMGgx
+MIIDWTCCAkECFFsSgyQAZHRltRAdi9SrK2ZHRa1/MA0GCSqGSIb3DQEBCwUAMGgx
 CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQHDAtTYW50
 YSBDbGFyYTEaMBgGA1UECgwRQmV5b25kIEFnZ3JhdmF0ZWQxEjAQBgNVBAMMCWxv
-Y2FsaG9zdDAeFw0yMDA1MDcyMTMzMDFaFw0yMTA1MDcyMTMzMDFaMGgxCzAJBgNV
-BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQHDAtTYW50YSBDbGFy
-YTEaMBgGA1UECgwRQmV5b25kIEFnZ3JhdmF0ZWQxEjAQBgNVBAMMCWxvY2FsaG9z
-dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPdz0Tom1NSujwxYFhG2
-MnqTTU5F9E5OwnO9svlXchXozJSoYpuFG43ZI/9exVmhQKZ4WwJUX74beYuZh611
-S1v9nAiAX+w3lpaiH/9gNH9PaR6kyOTveS9DtHqHlsHm9Ahuls/6mIlHVLsfGVcS
-DDIu5eYqBU0Xq1RYm3+9EUtEOLPQGfcaSUTnI6AkZ55TcJiKhq0CIoTpv/I+7mlw
-zsqPi2f2G7kI47bz1aiXeh34jelKR321fKl1/DW3F0CLSj0/u4gMgNIgPB/tHIKj
-GiNnvJTE7ZDSV34oUmqKhKkUixwjFHUFpMislpIJTsefzaKE4NLa57g5qgAnaofw
-m1UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEASZx0QGNR5DT8vUgEBTMD1OKG3rFw
-zXLI1Lsn5nIMSGkL7aIlx7D8lbdvy0OS+Cg8jE256yiM7cZTF07rKwUeI2v/wDrX
-KP9qfMhonICrbQyKlZ6J4hLVV9wCkYQnMqwS+uSH1l1X+qr3ZCcamgTZ2hrhJFy4
-HEeoC4qdL0+uM2NhrjmPBvqMq9hYWe3nAREmRjSAxBMawjThldLqQCooyvtMskkn
-QAzPte/qvP4kWRpI+KQEv9Rc8iI9PNCF9+W4zl6pIyRDRVYWx3C1PSdniaTc/yDQ
-FL5UbuZ5ujUOdvMy1yAlcTiDVo+Ke7ybAK9FhEBxMPELyTFTY0GVKI46QA==
+Y2FsaG9zdDAgFw0yMTA1MDgwNjUwMDBaGA8yMjk1MDIyMDA2NTAwMFowaDELMAkG
+A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFDASBgNVBAcMC1NhbnRhIENs
+YXJhMRowGAYDVQQKDBFCZXlvbmQgQWdncmF2YXRlZDESMBAGA1UEAwwJbG9jYWxo
+b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxijHKeNl7nlYwn3f
+6CG8MP3XDVI3bc4v+XIJElH2mrNz8AUd0I9FoJVEdCDnRIO1Vb7sN/Wn9fxMQQv2
+ReHE56kR36Ca19G5VFo789gt7268ibpV7QQ117aoeEdw7kpOukKUG87Q7bZWlsZ3
+FX/nxSv1Hnv5BABxo0uyM8tv5KGXWwR8bsmFCCEr8i2AtglOnyVSV3Ey18Vb/mgt
++E4YE6WobTAiP8UdEKTPdnBms9IcHNknTB+ux910xDH4z9fWv8+4Bp4mH43xvbrt
+GJEzDl1xQ+Mrzc7Hk0FJqUlT6WHuyM9Tk7jspmhXxggtecy0CGB/hSo//urrUu2L
+EUABgQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBKNKU5S6HmO6atYLFmBfBk2ms/
+ZkDNHtDxnKLmsX3CFthRI3mGz1oEaMzYb2G2ufDxzh4/x2DM4iffRgj2knHSGlun
+3NYClZLM18/KTpXF26bycnixVGVw26jG0WHQTSdCS5VLz8CSg1O/487aEC5sKtoe
+LSk4KMpDFzD0+Esbkf/1aw0VyxsxjHRnxIMheK4cUt3y5I5qIKS2qK0W9ZuWr1Er
+dkYKCVumZKxVuMhHVOck6zLC5lZjefkFtTtyLe2nKIvXNWcHyuKqmLfafhEVSlKP
+Idg90Qo9+TY4bwQ8sMRVcVlnnChGvDtmeBXz4xwyFg8Pzhvwa86PlwMISi/m
 -----END CERTIFICATE-----
diff --git a/host/frontend/webrtc_operator/certs/server.key b/host/frontend/webrtc_operator/certs/server.key
index 616a4ce..a6b8f9f 100644
--- a/host/frontend/webrtc_operator/certs/server.key
+++ b/host/frontend/webrtc_operator/certs/server.key
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-MIIEpgIBAAKCAQEA93PROibU1K6PDFgWEbYyepNNTkX0Tk7Cc72y+VdyFejMlKhi
-m4Ubjdkj/17FWaFApnhbAlRfvht5i5mHrXVLW/2cCIBf7DeWlqIf/2A0f09pHqTI
-5O95L0O0eoeWweb0CG6Wz/qYiUdUux8ZVxIMMi7l5ioFTRerVFibf70RS0Q4s9AZ
-9xpJROcjoCRnnlNwmIqGrQIihOm/8j7uaXDOyo+LZ/YbuQjjtvPVqJd6HfiN6UpH
-fbV8qXX8NbcXQItKPT+7iAyA0iA8H+0cgqMaI2e8lMTtkNJXfihSaoqEqRSLHCMU
-dQWkyKyWkglOx5/NooTg0trnuDmqACdqh/CbVQIDAQABAoIBAQCnBGrxvwfjzTYL
-9OBgcAM+LHH/JMQynoIssJs+JEGCfDCpHcYAhiUE5syfLo4xYt9J/O4gcmZ04AJ3
-sNacwxBsNI6+Rjd4LkTbwu2p5ntIeobPAhX+P4wh1KbaFO4yTfnkPxBXrCKMdbLA
-4cqutCW7MWBGq5IMaK9hLLU30Jr9muiEIQ8tnexghJ4SiHnpju471KD9N77dRVZp
-TmUnIlJLqYMNoPj0nLwy6p1isq1KSg56j27OooL8piqOp2cy82GqjNeCfFcW74dJ
-lYmxILLseJiJvM2nlfFdSOvSOtaBkSG5oKsOO1K56u3cXhwSXChu5b8ewNjG5d+Y
-KY5obpV5AoGBAP/nJGxv7ljsg/pAqfWOwR5g6Iz9JxpgweubVt9fvnS+2Pj4akrJ
-1NZZCB/9zFbYf2Of2VMBdrNw7eS2+QDwj2TYPBjIH3WMPSByyyQaNna47b26Sxu2
-kCMhkoEMvwXEEQrLZ7I/sHgzYIEGg/9yLUJmzfdqpACU4bA1zkzKQ6tnAoGBAPeL
-2qn7ch3vlVLK46tm99Jkbw2SxoJajlZ4p2kpe/EexyqInfZJ58IKQNonqYNWMOwg
-aLeoyf+XsWgnuS4njMBPLmGTa/Pibs2b/0jrHP3mp2LVrRaL+wOUDM/gzmt7NeD+
-zZ8fD4Lsj8lXLfys5M5HrosF8c3TT8odFALjLwnjAoGBAKmH3qh8CsIchl6O4knM
-tgHDH6zvtS0TdsT4lzfKfSlomeNu5zP+vCL4vpo7EFlkehhs+JO1/4ZnRSLlWNcX
-h1e+rSmZwsWkD4bkpdGYEAbdAptTxJhqfNjZT+5wnEhcmRG2qU78RJONLdysjVv4
-ryUzaDYGDvpXp6CONMrIoMX3AoGBAJV/bbg4dauklD6i7yoFjmcOZo8A9EenHs0U
-Iq588jAlUUzbouIpsgBapt3ZFCOQOw1vaS55jjyAxRBM5SX9lqBRcYZWPNzWA+rC
-akMEUsb3tGEZAGZcdWSs1av5bVA14c0WtOGDJaAA87k5oDk3xRra6YtmNKkEE+zQ
-8NPple/XAoGBAJMpXdSP+Hgakv//HaQaBcenHk1f8v4b8t48NnXjou3ojfO8Wzgr
-odwlninHk1PaQD0XnFIISMVD9d2aSX8X1YQVHUQ9IQiA4hLy2lxZbP2K+LEaVKDm
-r7cjRKixJTIGm6vZK8pr1l5XD3657fQte4YLei5XA+i/UgFZyIA0KsKo
+MIIEpQIBAAKCAQEAxijHKeNl7nlYwn3f6CG8MP3XDVI3bc4v+XIJElH2mrNz8AUd
+0I9FoJVEdCDnRIO1Vb7sN/Wn9fxMQQv2ReHE56kR36Ca19G5VFo789gt7268ibpV
+7QQ117aoeEdw7kpOukKUG87Q7bZWlsZ3FX/nxSv1Hnv5BABxo0uyM8tv5KGXWwR8
+bsmFCCEr8i2AtglOnyVSV3Ey18Vb/mgt+E4YE6WobTAiP8UdEKTPdnBms9IcHNkn
+TB+ux910xDH4z9fWv8+4Bp4mH43xvbrtGJEzDl1xQ+Mrzc7Hk0FJqUlT6WHuyM9T
+k7jspmhXxggtecy0CGB/hSo//urrUu2LEUABgQIDAQABAoIBAQCt5QsiT1QcOpER
+3LSpWTF1LM2T+xp5Wf/vv4sGcLcge2q6r0LCy3gmu9ceseFB1vNDFBDn6sRCse2Z
+B45PNRk+0rfEr4Qy8PDafXUvP/7PpzX9B3BwVsmJS9n783W/J6Z+/f5LiOsAMIs8
+NV47l8sk1LZ+0fxs7pbK3pq7qUPANiEgKQ2F6PBkJkORuHmfhccC/a+qhAdsqwVB
+DjjwY0e5A/4fWqLiIUJGBopv0+df1TWsqfTq4lwaSDPY6dyI9E3MYVxKiYpbqgQ/
+N7QtjDjut6Zuw10bCgYyuEgy3Ab5Pmx6Hs6VGuYI6Hge4JeZ6TzZJ/cNnONZvGTF
+3356FYABAoGBAPgA+JYHDgXl4W6n9uMYQ4ZXL1UrYVxe8B1wWgt7DKD98/fD+Jj1
+KuRRUB2wLv/Jis+48QlAwLHQIL35WGpGm9C9j9dS3NO199BLrDY4fteSr67NrG9P
+Q0H+3F/7Dx7Scg5LNRYe6ZNvdfUD+9zoiaNHa9MMriK0ffc4O08RkA3BAoGBAMyM
+Y6c83Wek6GzPu5GCSMxEGBdqbx8T3iyEo4J23N3WAcfdIgSWZcjB2wNdNqa/RuHW
+QH6Gns5DLO3pYLU7R9DgLK5VE/Nq4nF0o7D57DnRkT+sZ3gYPdC7LiG3d5c+J7k1
+3pKL8yh7t2AgMpopfaY70wV4gL1K2qOLYfxbVGPBAoGBAK/Y2GpghDPwZOD2Xdt2
+R+LIjPpB8R3y/ySQlnhPfovkpYlHvkyOgiQz96+lTh32ROO2ycn6zOcHoT+yvltU
+x4TB9G0EBypie12JWolzk5S9IK68jQi71f/Ee3Pe60C6jT7PWsvdjVcKEERz17Ey
+fO12ZeDWu95Fxo91orAUzuTBAoGAC2xbtF9Fzh/7ivge9YVdI2s6HTSoeAfYBIxz
+xTl2JD1rZAoJeFAd5xRMcuelwbI09y/L8kT6YXKG89JwwC5LWHLsi9/ceV+ivctR
+yPRsKN53SiMKtD5GVX3ematxVlT2SvWjNHP0ZHJkT039BXcDuWDl7AxKxEeF5lRG
+aJ2BHQECgYEA0y67wxrbrgLGnur1J3nMN/sXmaTp6aDg/fTJyaaYhAzt+EUIn3MC
+nqVrWC35lpD5TO1fo8kfyaQH94zQxkywVb018caXHotjHC+EN6VnrG8cXmEvXU8E
+rUmEzHAxy548ZAgV6I/2kIrDEzijElvq7Geq2MzBkOUnFpD2y030iQ0=
 -----END RSA PRIVATE KEY-----
diff --git a/host/frontend/webrtc_operator/certs/server.p12 b/host/frontend/webrtc_operator/certs/server.p12
index 87a94c5..80920ba 100644
--- a/host/frontend/webrtc_operator/certs/server.p12
+++ b/host/frontend/webrtc_operator/certs/server.p12
Binary files differ
diff --git a/host/frontend/webrtc_operator/certs/trusted.pem b/host/frontend/webrtc_operator/certs/trusted.pem
index 8097b16..0c50578 100644
--- a/host/frontend/webrtc_operator/certs/trusted.pem
+++ b/host/frontend/webrtc_operator/certs/trusted.pem
@@ -2,69 +2,69 @@
     Data:
         Version: 1 (0x0)
         Serial Number:
-            15:c1:c9:dc:34:7b:fe:e5:df:57:7d:7f:33:bb:be:b0:71:03:0e:32
+            5b:12:83:24:00:64:74:65:b5:10:1d:8b:d4:ab:2b:66:47:45:ad:7f
         Signature Algorithm: sha256WithRSAEncryption
         Issuer: C = US, ST = California, L = Santa Clara, O = Beyond Aggravated, CN = localhost
         Validity
-            Not Before: May  7 21:33:01 2020 GMT
-            Not After : May  7 21:33:01 2021 GMT
+            Not Before: May  8 06:50:00 2021 GMT
+            Not After : Feb 20 06:50:00 2295 GMT
         Subject: C = US, ST = California, L = Santa Clara, O = Beyond Aggravated, CN = localhost
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 RSA Public-Key: (2048 bit)
                 Modulus:
-                    00:f7:73:d1:3a:26:d4:d4:ae:8f:0c:58:16:11:b6:
-                    32:7a:93:4d:4e:45:f4:4e:4e:c2:73:bd:b2:f9:57:
-                    72:15:e8:cc:94:a8:62:9b:85:1b:8d:d9:23:ff:5e:
-                    c5:59:a1:40:a6:78:5b:02:54:5f:be:1b:79:8b:99:
-                    87:ad:75:4b:5b:fd:9c:08:80:5f:ec:37:96:96:a2:
-                    1f:ff:60:34:7f:4f:69:1e:a4:c8:e4:ef:79:2f:43:
-                    b4:7a:87:96:c1:e6:f4:08:6e:96:cf:fa:98:89:47:
-                    54:bb:1f:19:57:12:0c:32:2e:e5:e6:2a:05:4d:17:
-                    ab:54:58:9b:7f:bd:11:4b:44:38:b3:d0:19:f7:1a:
-                    49:44:e7:23:a0:24:67:9e:53:70:98:8a:86:ad:02:
-                    22:84:e9:bf:f2:3e:ee:69:70:ce:ca:8f:8b:67:f6:
-                    1b:b9:08:e3:b6:f3:d5:a8:97:7a:1d:f8:8d:e9:4a:
-                    47:7d:b5:7c:a9:75:fc:35:b7:17:40:8b:4a:3d:3f:
-                    bb:88:0c:80:d2:20:3c:1f:ed:1c:82:a3:1a:23:67:
-                    bc:94:c4:ed:90:d2:57:7e:28:52:6a:8a:84:a9:14:
-                    8b:1c:23:14:75:05:a4:c8:ac:96:92:09:4e:c7:9f:
-                    cd:a2:84:e0:d2:da:e7:b8:39:aa:00:27:6a:87:f0:
-                    9b:55
+                    00:c6:28:c7:29:e3:65:ee:79:58:c2:7d:df:e8:21:
+                    bc:30:fd:d7:0d:52:37:6d:ce:2f:f9:72:09:12:51:
+                    f6:9a:b3:73:f0:05:1d:d0:8f:45:a0:95:44:74:20:
+                    e7:44:83:b5:55:be:ec:37:f5:a7:f5:fc:4c:41:0b:
+                    f6:45:e1:c4:e7:a9:11:df:a0:9a:d7:d1:b9:54:5a:
+                    3b:f3:d8:2d:ef:6e:bc:89:ba:55:ed:04:35:d7:b6:
+                    a8:78:47:70:ee:4a:4e:ba:42:94:1b:ce:d0:ed:b6:
+                    56:96:c6:77:15:7f:e7:c5:2b:f5:1e:7b:f9:04:00:
+                    71:a3:4b:b2:33:cb:6f:e4:a1:97:5b:04:7c:6e:c9:
+                    85:08:21:2b:f2:2d:80:b6:09:4e:9f:25:52:57:71:
+                    32:d7:c5:5b:fe:68:2d:f8:4e:18:13:a5:a8:6d:30:
+                    22:3f:c5:1d:10:a4:cf:76:70:66:b3:d2:1c:1c:d9:
+                    27:4c:1f:ae:c7:dd:74:c4:31:f8:cf:d7:d6:bf:cf:
+                    b8:06:9e:26:1f:8d:f1:bd:ba:ed:18:91:33:0e:5d:
+                    71:43:e3:2b:cd:ce:c7:93:41:49:a9:49:53:e9:61:
+                    ee:c8:cf:53:93:b8:ec:a6:68:57:c6:08:2d:79:cc:
+                    b4:08:60:7f:85:2a:3f:fe:ea:eb:52:ed:8b:11:40:
+                    01:81
                 Exponent: 65537 (0x10001)
     Signature Algorithm: sha256WithRSAEncryption
-         49:9c:74:40:63:51:e4:34:fc:bd:48:04:05:33:03:d4:e2:86:
-         de:b1:70:cd:72:c8:d4:bb:27:e6:72:0c:48:69:0b:ed:a2:25:
-         c7:b0:fc:95:b7:6f:cb:43:92:f8:28:3c:8c:4d:b9:eb:28:8c:
-         ed:c6:53:17:4e:eb:2b:05:1e:23:6b:ff:c0:3a:d7:28:ff:6a:
-         7c:c8:68:9c:80:ab:6d:0c:8a:95:9e:89:e2:12:d5:57:dc:02:
-         91:84:27:32:ac:12:fa:e4:87:d6:5d:57:fa:aa:f7:64:27:1a:
-         9a:04:d9:da:1a:e1:24:5c:b8:1c:47:a8:0b:8a:9d:2f:4f:ae:
-         33:63:61:ae:39:8f:06:fa:8c:ab:d8:58:59:ed:e7:01:11:26:
-         46:34:80:c4:13:1a:c2:34:e1:95:d2:ea:40:2a:28:ca:fb:4c:
-         b2:49:27:40:0c:cf:b5:ef:ea:bc:fe:24:59:1a:48:f8:a4:04:
-         bf:d4:5c:f2:22:3d:3c:d0:85:f7:e5:b8:ce:5e:a9:23:24:43:
-         45:56:16:c7:70:b5:3d:27:67:89:a4:dc:ff:20:d0:14:be:54:
-         6e:e6:79:ba:35:0e:76:f3:32:d7:20:25:71:38:83:56:8f:8a:
-         7b:bc:9b:00:af:45:84:40:71:30:f1:0b:c9:31:53:63:41:95:
-         28:8e:3a:40
+         4a:34:a5:39:4b:a1:e6:3b:a6:ad:60:b1:66:05:f0:64:da:6b:
+         3f:66:40:cd:1e:d0:f1:9c:a2:e6:b1:7d:c2:16:d8:51:23:79:
+         86:cf:5a:04:68:cc:d8:6f:61:b6:b9:f0:f1:ce:1e:3f:c7:60:
+         cc:e2:27:df:46:08:f6:92:71:d2:1a:5b:a7:dc:d6:02:95:92:
+         cc:d7:cf:ca:4e:95:c5:db:a6:f2:72:78:b1:54:65:70:db:a8:
+         c6:d1:61:d0:4d:27:42:4b:95:4b:cf:c0:92:83:53:bf:e3:ce:
+         da:10:2e:6c:2a:da:1e:2d:29:38:28:ca:43:17:30:f4:f8:4b:
+         1b:91:ff:f5:6b:0d:15:cb:1b:31:8c:74:67:c4:83:21:78:ae:
+         1c:52:dd:f2:e4:8e:6a:20:a4:b6:a8:ad:16:f5:9b:96:af:51:
+         2b:76:46:0a:09:5b:a6:64:ac:55:b8:c8:47:54:e7:24:eb:32:
+         c2:e6:56:63:79:f9:05:b5:3b:72:2d:ed:a7:28:8b:d7:35:67:
+         07:ca:e2:aa:98:b7:da:7e:11:15:4a:52:8f:21:d8:3d:d1:0a:
+         3d:f9:36:38:6f:04:3c:b0:c4:55:71:59:67:9c:28:46:bc:3b:
+         66:78:15:f3:e3:1c:32:16:0f:0f:ce:1b:f0:6b:ce:8f:97:03:
+         08:4a:2f:e6
 -----BEGIN CERTIFICATE-----
-MIIDVzCCAj8CFBXBydw0e/7l31d9fzO7vrBxAw4yMA0GCSqGSIb3DQEBCwUAMGgx
+MIIDWTCCAkECFFsSgyQAZHRltRAdi9SrK2ZHRa1/MA0GCSqGSIb3DQEBCwUAMGgx
 CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQHDAtTYW50
 YSBDbGFyYTEaMBgGA1UECgwRQmV5b25kIEFnZ3JhdmF0ZWQxEjAQBgNVBAMMCWxv
-Y2FsaG9zdDAeFw0yMDA1MDcyMTMzMDFaFw0yMTA1MDcyMTMzMDFaMGgxCzAJBgNV
-BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQHDAtTYW50YSBDbGFy
-YTEaMBgGA1UECgwRQmV5b25kIEFnZ3JhdmF0ZWQxEjAQBgNVBAMMCWxvY2FsaG9z
-dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPdz0Tom1NSujwxYFhG2
-MnqTTU5F9E5OwnO9svlXchXozJSoYpuFG43ZI/9exVmhQKZ4WwJUX74beYuZh611
-S1v9nAiAX+w3lpaiH/9gNH9PaR6kyOTveS9DtHqHlsHm9Ahuls/6mIlHVLsfGVcS
-DDIu5eYqBU0Xq1RYm3+9EUtEOLPQGfcaSUTnI6AkZ55TcJiKhq0CIoTpv/I+7mlw
-zsqPi2f2G7kI47bz1aiXeh34jelKR321fKl1/DW3F0CLSj0/u4gMgNIgPB/tHIKj
-GiNnvJTE7ZDSV34oUmqKhKkUixwjFHUFpMislpIJTsefzaKE4NLa57g5qgAnaofw
-m1UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEASZx0QGNR5DT8vUgEBTMD1OKG3rFw
-zXLI1Lsn5nIMSGkL7aIlx7D8lbdvy0OS+Cg8jE256yiM7cZTF07rKwUeI2v/wDrX
-KP9qfMhonICrbQyKlZ6J4hLVV9wCkYQnMqwS+uSH1l1X+qr3ZCcamgTZ2hrhJFy4
-HEeoC4qdL0+uM2NhrjmPBvqMq9hYWe3nAREmRjSAxBMawjThldLqQCooyvtMskkn
-QAzPte/qvP4kWRpI+KQEv9Rc8iI9PNCF9+W4zl6pIyRDRVYWx3C1PSdniaTc/yDQ
-FL5UbuZ5ujUOdvMy1yAlcTiDVo+Ke7ybAK9FhEBxMPELyTFTY0GVKI46QA==
+Y2FsaG9zdDAgFw0yMTA1MDgwNjUwMDBaGA8yMjk1MDIyMDA2NTAwMFowaDELMAkG
+A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFDASBgNVBAcMC1NhbnRhIENs
+YXJhMRowGAYDVQQKDBFCZXlvbmQgQWdncmF2YXRlZDESMBAGA1UEAwwJbG9jYWxo
+b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxijHKeNl7nlYwn3f
+6CG8MP3XDVI3bc4v+XIJElH2mrNz8AUd0I9FoJVEdCDnRIO1Vb7sN/Wn9fxMQQv2
+ReHE56kR36Ca19G5VFo789gt7268ibpV7QQ117aoeEdw7kpOukKUG87Q7bZWlsZ3
+FX/nxSv1Hnv5BABxo0uyM8tv5KGXWwR8bsmFCCEr8i2AtglOnyVSV3Ey18Vb/mgt
++E4YE6WobTAiP8UdEKTPdnBms9IcHNknTB+ux910xDH4z9fWv8+4Bp4mH43xvbrt
+GJEzDl1xQ+Mrzc7Hk0FJqUlT6WHuyM9Tk7jspmhXxggtecy0CGB/hSo//urrUu2L
+EUABgQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBKNKU5S6HmO6atYLFmBfBk2ms/
+ZkDNHtDxnKLmsX3CFthRI3mGz1oEaMzYb2G2ufDxzh4/x2DM4iffRgj2knHSGlun
+3NYClZLM18/KTpXF26bycnixVGVw26jG0WHQTSdCS5VLz8CSg1O/487aEC5sKtoe
+LSk4KMpDFzD0+Esbkf/1aw0VyxsxjHRnxIMheK4cUt3y5I5qIKS2qK0W9ZuWr1Er
+dkYKCVumZKxVuMhHVOck6zLC5lZjefkFtTtyLe2nKIvXNWcHyuKqmLfafhEVSlKP
+Idg90Qo9+TY4bwQ8sMRVcVlnnChGvDtmeBXz4xwyFg8Pzhvwa86PlwMISi/m
 -----END CERTIFICATE-----
diff --git a/host/frontend/webrtc_operator/server.cpp b/host/frontend/webrtc_operator/server.cpp
index 565676a..bbc862c 100644
--- a/host/frontend/webrtc_operator/server.cpp
+++ b/host/frontend/webrtc_operator/server.cpp
@@ -31,7 +31,9 @@
 DEFINE_bool(use_secure_http, true, "Whether to use HTTPS or HTTP.");
 DEFINE_string(assets_dir, "webrtc",
               "Directory with location of webpage assets.");
-DEFINE_string(certs_dir, "webrtc/certs", "Directory to certificates.");
+DEFINE_string(certs_dir, "webrtc/certs",
+              "Directory to certificates. It must contain a server.crt file, a "
+              "server.key file and (optionally) a CA.crt file.");
 DEFINE_string(stun_server, "stun.l.google.com:19302",
               "host:port of STUN server to use for public address resolution");
 
@@ -50,8 +52,13 @@
   cuttlefish::DeviceRegistry device_registry;
   cuttlefish::ServerConfig server_config({FLAGS_stun_server});
 
-  cuttlefish::WebSocketServer wss(
-        "webrtc-operator", FLAGS_certs_dir, FLAGS_assets_dir, FLAGS_http_server_port);
+  cuttlefish::WebSocketServer wss =
+      FLAGS_use_secure_http
+          ? cuttlefish::WebSocketServer("webrtc-operator", FLAGS_certs_dir,
+                                        FLAGS_assets_dir,
+                                        FLAGS_http_server_port)
+          : cuttlefish::WebSocketServer("webrtc-operator", FLAGS_assets_dir,
+                                        FLAGS_http_server_port);
 
   auto device_handler_factory_p =
       std::unique_ptr<cuttlefish::WebSocketHandlerFactory>(
diff --git a/host/frontend/webrtc_operator/signal_handler.cpp b/host/frontend/webrtc_operator/signal_handler.cpp
index d1d8bf3..92ceb26 100644
--- a/host/frontend/webrtc_operator/signal_handler.cpp
+++ b/host/frontend/webrtc_operator/signal_handler.cpp
@@ -60,6 +60,24 @@
   handleMessage(type, json_message);
 }
 
+void SignalHandler::OnReceive(const uint8_t* msg, size_t len, bool binary,
+                              bool is_final) {
+  if (is_final) {
+    if (receive_buffer_.empty()) {
+      // no previous data - receive as-is
+      OnReceive(msg, len, binary);
+    } else {
+      // concatenate to previous data and receive
+      receive_buffer_.insert(receive_buffer_.end(), msg, msg + len);
+      OnReceive(receive_buffer_.data(), receive_buffer_.size(), binary);
+      receive_buffer_.clear();
+    }
+  } else {
+    // buffer up incomplete messages
+    receive_buffer_.insert(receive_buffer_.end(), msg, msg + len);
+  }
+}
+
 void SignalHandler::SendServerConfig() {
   // Call every time to allow config changes?
   auto reply = server_config_.ToJson();
diff --git a/host/frontend/webrtc_operator/signal_handler.h b/host/frontend/webrtc_operator/signal_handler.h
index c278517..a0e813c 100644
--- a/host/frontend/webrtc_operator/signal_handler.h
+++ b/host/frontend/webrtc_operator/signal_handler.h
@@ -29,6 +29,8 @@
 class SignalHandler : public WebSocketHandler {
  public:
   void OnReceive(const uint8_t* msg, size_t len, bool binary) override;
+  void OnReceive(const uint8_t* msg, size_t len, bool binary,
+                 bool is_final) override;
   void OnConnected() override;
  protected:
   SignalHandler(struct lws* wsi, DeviceRegistry* registry,
@@ -43,5 +45,6 @@
 
   DeviceRegistry* registry_;
   const ServerConfig& server_config_;
+  std::vector<uint8_t> receive_buffer_;
 };
 }  // namespace cuttlefish
diff --git a/host/libs/audio_connector/commands.cpp b/host/libs/audio_connector/commands.cpp
index 42536d5..b77c726 100644
--- a/host/libs/audio_connector/commands.cpp
+++ b/host/libs/audio_connector/commands.cpp
@@ -15,6 +15,8 @@
 
 #include "host/libs/audio_connector/commands.h"
 
+#include <algorithm>
+
 #include <android-base/logging.h>
 
 #include "host/libs/audio_connector/shm_layout.h"
@@ -27,6 +29,50 @@
       << " went out of scope without reply";
 }
 
+JackInfoCommand::JackInfoCommand(uint32_t start_id, size_t count,
+                                 virtio_snd_jack_info* jack_info)
+    : InfoCommand(AudioCommandType::VIRTIO_SND_R_CHMAP_INFO, start_id, count,
+                  jack_info) {}
+
+void JackInfoCommand::Reply(AudioStatus status,
+                            const std::vector<virtio_snd_jack_info>& reply) {
+  MarkReplied(status);
+  if (status != AudioStatus::VIRTIO_SND_S_OK) {
+    return;
+  }
+  CHECK(reply.size() == count())
+      << "Returned unmatching info count: " << reply.size() << " vs "
+      << count();
+  for (int i = 0; i < reply.size(); ++i) {
+    info_reply()[i] = reply[i];
+  }
+}
+
+ChmapInfoCommand::ChmapInfoCommand(uint32_t start_id, size_t count,
+                                   virtio_snd_chmap_info* chmap_info)
+    : InfoCommand(AudioCommandType::VIRTIO_SND_R_CHMAP_INFO, start_id, count,
+                  chmap_info) {}
+
+void ChmapInfoCommand::Reply(AudioStatus status,
+                             const std::vector<virtio_snd_chmap_info>& reply) {
+  MarkReplied(status);
+  if (status != AudioStatus::VIRTIO_SND_S_OK) {
+    return;
+  }
+  CHECK(reply.size() == count())
+      << "Returned unmatching info count: " << reply.size() << " vs "
+      << count();
+  for (int i = 0; i < reply.size(); ++i) {
+    info_reply()[i].hdr.hda_fn_nid = Le32(reply[i].hdr.hda_fn_nid);
+    info_reply()[i].direction = reply[i].direction;
+    auto channels = std::min(VIRTIO_SND_CHMAP_MAX_SIZE, reply[i].channels);
+    info_reply()[i].channels = channels;
+    for (int j = 0; j < channels; ++j) {
+	    info_reply()[i].positions[j] = reply[i].positions[j];
+    }
+  }
+}
+
 StreamInfoCommand::StreamInfoCommand(uint32_t start_id, size_t count,
                                      virtio_snd_pcm_info* pcm_info)
     : InfoCommand(AudioCommandType::VIRTIO_SND_R_PCM_INFO, start_id, count,
diff --git a/host/libs/audio_connector/commands.h b/host/libs/audio_connector/commands.h
index 849dc12..1ae2e6b 100644
--- a/host/libs/audio_connector/commands.h
+++ b/host/libs/audio_connector/commands.h
@@ -60,6 +60,24 @@
   R* info_reply_;
 };
 
+class ChmapInfoCommand : public InfoCommand<virtio_snd_chmap_info> {
+ public:
+  ChmapInfoCommand(uint32_t start_id, size_t count,
+                   virtio_snd_chmap_info* chmap_info);
+
+  void Reply(AudioStatus status,
+             const std::vector<virtio_snd_chmap_info>& reply);
+};
+
+class JackInfoCommand : public InfoCommand<virtio_snd_jack_info> {
+ public:
+  JackInfoCommand(uint32_t start_id, size_t count,
+                   virtio_snd_jack_info* jack_info);
+
+  void Reply(AudioStatus status,
+             const std::vector<virtio_snd_jack_info>& reply);
+};
+
 class StreamInfoCommand : public InfoCommand<virtio_snd_pcm_info> {
  public:
   StreamInfoCommand(uint32_t start_id, size_t count,
diff --git a/host/libs/audio_connector/server.cpp b/host/libs/audio_connector/server.cpp
index fd7e107..4f0570c 100644
--- a/host/libs/audio_connector/server.cpp
+++ b/host/libs/audio_connector/server.cpp
@@ -21,6 +21,7 @@
 #include <unistd.h>
 
 #include <utility>
+#include <vector>
 
 #include <android-base/logging.h>
 
@@ -244,8 +245,38 @@
       executor.StopStream(cmd);
       return CmdReply(cmd.status());
     }
-    case AudioCommandType::VIRTIO_SND_R_CHMAP_INFO:
-    case AudioCommandType::VIRTIO_SND_R_JACK_INFO:
+    case AudioCommandType::VIRTIO_SND_R_CHMAP_INFO: {
+      if (recv_size < sizeof(virtio_snd_query_info)) {
+        LOG(ERROR) << "Received QUERY_INFO message is too small: " << recv_size;
+        return false;
+      }
+      auto query_info = reinterpret_cast<const virtio_snd_query_info*>(cmd_hdr);
+      auto info_count = query_info->count.as_uint32_t();
+      auto start_id = query_info->start_id.as_uint32_t();
+      std::unique_ptr<virtio_snd_chmap_info[]> reply(
+          new virtio_snd_chmap_info[info_count]);
+      ChmapInfoCommand cmd(start_id, info_count, reply.get());
+
+      executor.ChmapsInfo(cmd);
+      return CmdReply(cmd.status(), reply.get(),
+                      info_count * sizeof(reply[0]));
+    }
+    case AudioCommandType::VIRTIO_SND_R_JACK_INFO: {
+      if (recv_size < sizeof(virtio_snd_query_info)) {
+        LOG(ERROR) << "Received QUERY_INFO message is too small: " << recv_size;
+        return false;
+      }
+      auto query_info = reinterpret_cast<const virtio_snd_query_info*>(cmd_hdr);
+      auto info_count = query_info->count.as_uint32_t();
+      auto start_id = query_info->start_id.as_uint32_t();
+      std::unique_ptr<virtio_snd_jack_info[]> reply(
+          new virtio_snd_jack_info[info_count]);
+      JackInfoCommand cmd(start_id, info_count, reply.get());
+
+      executor.JacksInfo(cmd);
+      return CmdReply(cmd.status(), reply.get(),
+                      info_count * sizeof(reply[0]));
+    }
     case AudioCommandType::VIRTIO_SND_R_JACK_REMAP:
       LOG(ERROR) << "Unsupported command type: " << cmd_hdr->code.as_uint32_t();
       return CmdReply(AudioStatus::VIRTIO_SND_S_NOT_SUPP);
@@ -302,21 +333,17 @@
   virtio_snd_hdr vio_status = {
       .code = Le32(static_cast<uint32_t>(status)),
   };
-  auto status_sent = control_socket_->Send(&vio_status, sizeof(vio_status), 0);
-  if (status_sent < sizeof(vio_status)) {
+  std::vector<uint8_t> buffer(sizeof(vio_status) + size, 0);
+  std::memcpy(buffer.data(), &vio_status, sizeof(vio_status));
+  if (data) {
+    std::memcpy(buffer.data() + sizeof(vio_status), data, size);
+  }
+  auto status_sent = control_socket_->Send(buffer.data(), buffer.size(), 0);
+  if (status_sent < sizeof(vio_status) + size) {
     LOG(ERROR) << "Failed to send entire command status: "
                << control_socket_->StrError();
     return false;
   }
-  if (status != AudioStatus::VIRTIO_SND_S_OK || size == 0) {
-    return true;
-  }
-  auto payload_sent = control_socket_->Send(data, size, 0);
-  if (payload_sent < size) {
-    LOG(ERROR) << "Failed to send entire command response payload: "
-               << control_socket_->StrError();
-    return false;
-  }
   return true;
 }
 
@@ -341,7 +368,7 @@
 ssize_t AudioClientConnection::ReceiveMsg(SharedFD socket, void* buffer,
                                           size_t size) {
   auto read = socket->Recv(buffer, size, MSG_TRUNC);
-  CHECK(read <= size)
+  CHECK(read < 0 || read <= size)
       << "Received a msg bigger than the buffer, msg was truncated: " << read
       << " vs " << size;
   if (read == 0) {
diff --git a/host/libs/audio_connector/server.h b/host/libs/audio_connector/server.h
index 4ba2e7c..6fa14d9 100644
--- a/host/libs/audio_connector/server.h
+++ b/host/libs/audio_connector/server.h
@@ -41,6 +41,8 @@
   virtual void ReleaseStream(StreamControlCommand& cmd) = 0;
   virtual void StartStream(StreamControlCommand& cmd) = 0;
   virtual void StopStream(StreamControlCommand& cmd) = 0;
+  virtual void ChmapsInfo(ChmapInfoCommand& cmd) = 0;
+  virtual void JacksInfo(JackInfoCommand& cmd) = 0;
 
   // Implementations must call buffer.SendStatus() before destroying the buffer
   // to notify the other side of the release of the buffer. Failure to do so
diff --git a/host/libs/audio_connector/shm_layout.h b/host/libs/audio_connector/shm_layout.h
index 9148cb8..24000e6 100644
--- a/host/libs/audio_connector/shm_layout.h
+++ b/host/libs/audio_connector/shm_layout.h
@@ -51,7 +51,7 @@
   NOT_SET = static_cast<uint32_t>(-1),
 };
 
-enum class AudioStreamDirection : uint32_t {
+enum class AudioStreamDirection : uint8_t {
   VIRTIO_SND_D_OUTPUT = 0,
   VIRTIO_SND_D_INPUT
 };
@@ -104,6 +104,47 @@
   VIRTIO_SND_PCM_RATE_384000
 };
 
+/* standard channel position definition */
+enum AudioChannelMap : uint8_t {
+  VIRTIO_SND_CHMAP_NONE = 0,  /* undefined */
+  VIRTIO_SND_CHMAP_NA,        /* silent */
+  VIRTIO_SND_CHMAP_MONO,      /* mono stream */
+  VIRTIO_SND_CHMAP_FL,        /* front left */
+  VIRTIO_SND_CHMAP_FR,        /* front right */
+  VIRTIO_SND_CHMAP_RL,        /* rear left */
+  VIRTIO_SND_CHMAP_RR,        /* rear right */
+  VIRTIO_SND_CHMAP_FC,        /* front center */
+  VIRTIO_SND_CHMAP_LFE,       /* low frequency (LFE) */
+  VIRTIO_SND_CHMAP_SL,        /* side left */
+  VIRTIO_SND_CHMAP_SR,        /* side right */
+  VIRTIO_SND_CHMAP_RC,        /* rear center */
+  VIRTIO_SND_CHMAP_FLC,       /* front left center */
+  VIRTIO_SND_CHMAP_FRC,       /* front right center */
+  VIRTIO_SND_CHMAP_RLC,       /* rear left center */
+  VIRTIO_SND_CHMAP_RRC,       /* rear right center */
+  VIRTIO_SND_CHMAP_FLW,       /* front left wide */
+  VIRTIO_SND_CHMAP_FRW,       /* front right wide */
+  VIRTIO_SND_CHMAP_FLH,       /* front left high */
+  VIRTIO_SND_CHMAP_FCH,       /* front center high */
+  VIRTIO_SND_CHMAP_FRH,       /* front right high */
+  VIRTIO_SND_CHMAP_TC,        /* top center */
+  VIRTIO_SND_CHMAP_TFL,       /* top front left */
+  VIRTIO_SND_CHMAP_TFR,       /* top front right */
+  VIRTIO_SND_CHMAP_TFC,       /* top front center */
+  VIRTIO_SND_CHMAP_TRL,       /* top rear left */
+  VIRTIO_SND_CHMAP_TRR,       /* top rear right */
+  VIRTIO_SND_CHMAP_TRC,       /* top rear center */
+  VIRTIO_SND_CHMAP_TFLC,      /* top front left center */
+  VIRTIO_SND_CHMAP_TFRC,      /* top front right center */
+  VIRTIO_SND_CHMAP_TSL,       /* top side left */
+  VIRTIO_SND_CHMAP_TSR,       /* top side right */
+  VIRTIO_SND_CHMAP_LLFE,      /* left LFE */
+  VIRTIO_SND_CHMAP_RLFE,      /* right LFE */
+  VIRTIO_SND_CHMAP_BC,        /* bottom center */
+  VIRTIO_SND_CHMAP_BLC,       /* bottom left center */
+  VIRTIO_SND_CHMAP_BRC        /* bottom right center */
+};
+
 struct virtio_snd_hdr {
   Le32 code;
 };
@@ -119,6 +160,29 @@
   Le32 hda_fn_nid;
 };
 
+/* supported jack features */
+enum AudioJackFeatures: uint8_t {
+  VIRTIO_SND_JACK_F_REMAP = 0
+};
+
+struct virtio_snd_jack_info {
+  struct virtio_snd_info hdr;
+  Le32 features; /* 1 << VIRTIO_SND_JACK_F_XXX */
+  Le32 hda_reg_defconf;
+  Le32 hda_reg_caps;
+  uint8_t connected;
+
+  uint8_t padding[7];
+};
+
+constexpr uint8_t VIRTIO_SND_CHMAP_MAX_SIZE = 18;
+struct virtio_snd_chmap_info {
+  struct virtio_snd_info hdr;
+  uint8_t direction;
+  uint8_t channels;
+  uint8_t positions[VIRTIO_SND_CHMAP_MAX_SIZE];
+};
+
 struct virtio_snd_pcm_info {
   struct virtio_snd_info hdr;
   Le32 features; /* 1 << VIRTIO_SND_PCM_F_XXX */
@@ -157,7 +221,7 @@
 };
 
 // Update this value when the msg layouts change
-const uint32_t VIOS_VERSION = 1;
+const uint32_t VIOS_VERSION = 2;
 
 struct VioSConfig {
   uint32_t version;
@@ -182,6 +246,8 @@
 #define ASSERT_VALID_MSG_TYPE(T, size) \
   static_assert(sizeof(T) == (size), #T " has the wrong size")
 ASSERT_VALID_MSG_TYPE(virtio_snd_query_info, 16);
+ASSERT_VALID_MSG_TYPE(virtio_snd_jack_info, 24);
+ASSERT_VALID_MSG_TYPE(virtio_snd_chmap_info, 24);
 ASSERT_VALID_MSG_TYPE(virtio_snd_pcm_info, 32);
 ASSERT_VALID_MSG_TYPE(virtio_snd_pcm_set_params, 24);
 ASSERT_VALID_MSG_TYPE(virtio_snd_pcm_hdr, 8);
@@ -189,4 +255,4 @@
 ASSERT_VALID_MSG_TYPE(IoStatusMsg, 16);
 #undef ASSERT_VALID_MSG_TYPE
 
-}  // namespace cuttlefish
\ No newline at end of file
+}  // namespace cuttlefish
diff --git a/host/libs/config/Android.bp b/host/libs/config/Android.bp
index 3697f4e..93c1fdd 100644
--- a/host/libs/config/Android.bp
+++ b/host/libs/config/Android.bp
@@ -20,11 +20,13 @@
 cc_library_static {
     name: "libcuttlefish_host_config",
     srcs: [
+        "adb_config.cpp",
         "bootconfig_args.cpp",
         "custom_actions.cpp",
         "cuttlefish_config.cpp",
         "cuttlefish_config_instance.cpp",
         "data_image.cpp",
+        "feature.cpp",
         "fetcher_config.cpp",
         "host_tools_version.cpp",
         "kernel_args.cpp",
@@ -35,9 +37,32 @@
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libbase",
+        "libfruit",
         "libgflags",
         "libjsoncpp",
         "libz",
     ],
     defaults: ["cuttlefish_host"],
 }
+
+cc_test_host {
+    name: "libcuttlefish_host_config_test",
+    srcs: [
+        "adb_config_test.cpp",
+    ],
+    static_libs: [
+        "libbase",
+        "libcuttlefish_fs",
+        "libcuttlefish_host_config",
+        "libcuttlefish_utils",
+    ],
+    shared_libs: [
+        "libfruit",
+        "libjsoncpp",
+        "liblog",
+    ],
+    defaults: ["cuttlefish_host"],
+    test_options: {
+        unit_test: true,
+    },
+}
diff --git a/host/libs/config/adb_config.cpp b/host/libs/config/adb_config.cpp
new file mode 100644
index 0000000..0f8f407
--- /dev/null
+++ b/host/libs/config/adb_config.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 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/config/adb_config.h"
+
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+#include <algorithm>
+#include <set>
+
+#include "host/libs/config/config_fragment.h"
+
+namespace cuttlefish {
+
+AdbConfig::AdbConfig() = default;
+
+static AdbMode StringToAdbMode(std::string mode) {
+  std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower);
+  if (mode == "vsock_tunnel") {
+    return AdbMode::VsockTunnel;
+  } else if (mode == "vsock_half_tunnel") {
+    return AdbMode::VsockHalfTunnel;
+  } else if (mode == "native_vsock") {
+    return AdbMode::NativeVsock;
+  } else {
+    return AdbMode::Unknown;
+  }
+}
+
+static std::string AdbModeToString(AdbMode mode) {
+  switch (mode) {
+    case AdbMode::VsockTunnel:
+      return "vsock_tunnel";
+    case AdbMode::VsockHalfTunnel:
+      return "vsock_half_tunnel";
+    case AdbMode::NativeVsock:
+      return "native_vsock";
+    case AdbMode::Unknown:  // fall through
+    default:
+      return "unknown";
+  }
+}
+
+void AdbConfig::set_adb_mode(const std::set<std::string>& modes) {
+  adb_mode_.clear();
+  for (auto& mode : modes) {
+    adb_mode_.insert(StringToAdbMode(mode));
+  }
+}
+void AdbConfig::set_adb_mode(const std::set<AdbMode>& modes) {
+  adb_mode_ = modes;
+}
+std::set<AdbMode> AdbConfig::adb_mode() const { return adb_mode_; }
+
+void AdbConfig::set_run_adb_connector(bool run_adb_connector) {
+  run_adb_connector_ = run_adb_connector;
+}
+bool AdbConfig::run_adb_connector() const { return run_adb_connector_; }
+
+std::string AdbConfig::Name() const { return "adb"; }
+
+constexpr char kMode[] = "mode";
+constexpr char kConnectorEnabled[] = "connector_enabled";
+Json::Value AdbConfig::Serialize() const {
+  Json::Value json;
+  json[kMode] = Json::Value(Json::arrayValue);
+  for (const auto& mode : adb_mode_) {
+    json[kMode].append(AdbModeToString(mode));
+  }
+  json[kConnectorEnabled] = run_adb_connector_;
+  return json;
+}
+bool AdbConfig::Deserialize(const Json::Value& json) {
+  if (!json.isMember(kMode) || json[kMode].type() != Json::arrayValue) {
+    LOG(ERROR) << "Invalid value for " << kMode;
+    return false;
+  }
+  adb_mode_.clear();
+  for (auto& mode : json[kMode]) {
+    if (mode.type() != Json::stringValue) {
+      LOG(ERROR) << "Invalid mode type" << mode;
+      return false;
+    }
+    adb_mode_.insert(StringToAdbMode(mode.asString()));
+  }
+  if (!json.isMember(kConnectorEnabled) ||
+      json[kConnectorEnabled].type() != Json::booleanValue) {
+    LOG(ERROR) << "Invalid value for " << kConnectorEnabled;
+    return false;
+  }
+  run_adb_connector_ = json[kConnectorEnabled].asBool();
+  return true;
+}
+
+fruit::Component<AdbConfig> AdbConfigComponent() {
+  return fruit::createComponent().addMultibinding<ConfigFragment, AdbConfig>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/adb_config.h b/host/libs/config/adb_config.h
new file mode 100644
index 0000000..6306f8c
--- /dev/null
+++ b/host/libs/config/adb_config.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 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 <fruit/fruit.h>
+#include <set>
+
+#include "host/libs/config/config_fragment.h"
+
+namespace cuttlefish {
+
+enum class AdbMode {
+  VsockTunnel,
+  VsockHalfTunnel,
+  NativeVsock,
+  Unknown,
+};
+
+class AdbConfig : public ConfigFragment {
+ public:
+  INJECT(AdbConfig());
+
+  void set_adb_mode(const std::set<std::string>& modes);
+  void set_adb_mode(const std::set<AdbMode>& modes);
+  std::set<AdbMode> adb_mode() const;
+
+  void set_run_adb_connector(bool run_adb_connector);
+  bool run_adb_connector() const;
+
+  // ConfigFragment
+  std::string Name() const override;
+  Json::Value Serialize() const override;
+  bool Deserialize(const Json::Value&) override;
+
+ private:
+  std::set<AdbMode> adb_mode_;
+  bool run_adb_connector_;
+};
+
+fruit::Component<AdbConfig> AdbConfigComponent();
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/adb_config_test.cpp b/host/libs/config/adb_config_test.cpp
new file mode 100644
index 0000000..ffef2ba
--- /dev/null
+++ b/host/libs/config/adb_config_test.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#include <host/libs/config/adb_config.h>
+
+#include <gtest/gtest.h>
+#include <string>
+
+namespace cuttlefish {
+
+TEST(AdbConfigTest, SaveRetrieve) {
+  AdbConfig config;
+  std::set<AdbMode> modes = {AdbMode::VsockTunnel, AdbMode::VsockHalfTunnel,
+                             AdbMode::NativeVsock, AdbMode::Unknown};
+  config.set_adb_mode(modes);
+  config.set_run_adb_connector(true);
+
+  ASSERT_EQ(config.adb_mode(), modes);
+  ASSERT_TRUE(config.run_adb_connector());
+}
+
+TEST(AdbConfigTest, SerializeDeserialize) {
+  AdbConfig config;
+  config.set_adb_mode({AdbMode::VsockTunnel, AdbMode::VsockHalfTunnel,
+                       AdbMode::NativeVsock, AdbMode::Unknown});
+  config.set_run_adb_connector(true);
+
+  AdbConfig config2;
+  ASSERT_TRUE(config2.Deserialize(config.Serialize()));
+  ASSERT_EQ(config.adb_mode(), config2.adb_mode());
+  ASSERT_EQ(config.run_adb_connector(), config2.run_adb_connector());
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/bootconfig_args.cpp b/host/libs/config/bootconfig_args.cpp
index c2bc1eb..4f493ea 100644
--- a/host/libs/config/bootconfig_args.cpp
+++ b/host/libs/config/bootconfig_args.cpp
@@ -24,6 +24,7 @@
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
 #include "host/libs/vm_manager/qemu_manager.h"
 #include "host/libs/vm_manager/vm_manager.h"
@@ -47,15 +48,6 @@
   return os.str();
 }
 
-std::string mac_to_str(const std::array<unsigned char, 6>& mac) {
-  std::ostringstream stream;
-  stream << std::hex << (int)mac[0];
-  for (int i = 1; i < 6; i++) {
-    stream << ":" << std::hex << (int)mac[i];
-  }
-  return stream.str();
-}
-
 // TODO(schuffelen): Move more of this into host/libs/vm_manager, as a
 // substitute for the vm_manager comparisons.
 std::vector<std::string> VmManagerBootconfig(const CuttlefishConfig& config) {
@@ -90,7 +82,13 @@
 
   bootconfig_args.push_back(
       concat("androidboot.serialno=", instance.serial_number()));
-  bootconfig_args.push_back(concat("androidboot.lcd_density=", config.dpi()));
+
+  // TODO(b/131884992): update to specify multiple once supported.
+  const auto display_configs = config.display_configs();
+  CHECK_GE(display_configs.size(), 1);
+  bootconfig_args.push_back(
+      concat("androidboot.lcd_density=", display_configs[0].dpi));
+
   bootconfig_args.push_back(
       concat("androidboot.setupwizard_mode=", config.setupwizard_mode()));
   if (!config.guest_enforce_security()) {
@@ -120,7 +118,7 @@
 
   if (config.enable_vehicle_hal_grpc_server() &&
       instance.vehicle_hal_server_port() &&
-      FileExists(config.vehicle_hal_grpc_server_binary())) {
+      FileExists(VehicleHalGrpcServerBinary())) {
     constexpr int vehicle_hal_server_cid = 2;
     bootconfig_args.push_back(concat(
         "androidboot.vendor.vehiclehal.server.cid=", vehicle_hal_server_cid));
@@ -143,15 +141,21 @@
                                      instance.frames_server_port()));
   }
 
+  if (instance.camera_server_port()) {
+    bootconfig_args.push_back(concat("androidboot.vsock_camera_port=",
+                                     instance.camera_server_port()));
+    bootconfig_args.push_back(
+        concat("androidboot.vsock_camera_cid=", instance.vsock_guest_cid()));
+  }
+
   if (config.enable_modem_simulator() &&
       instance.modem_simulator_ports() != "") {
     bootconfig_args.push_back(concat("androidboot.modem_simulator_ports=",
                                      instance.modem_simulator_ports()));
   }
 
-  // TODO(b/158131610): Set this in crosvm instead
-  bootconfig_args.push_back(concat("androidboot.wifi_mac_address=",
-                                   mac_to_str(instance.wifi_mac_address())));
+  bootconfig_args.push_back(
+      concat("androidboot.wifi_mac_prefix=", instance.wifi_mac_prefix()));
 
   bootconfig_args.push_back("androidboot.verifiedbootstate=orange");
 
diff --git a/common/libs/utils/size_utils.cpp b/host/libs/config/config_fragment.h
similarity index 64%
copy from common/libs/utils/size_utils.cpp
copy to host/libs/config/config_fragment.h
index 9f25445..cc1bfc1 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/libs/config/config_fragment.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -13,16 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#pragma once
 
-#include "common/libs/utils/size_utils.h"
-
-#include <unistd.h>
+#include <json/json.h>
+#include <memory>
+#include <string>
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+class ConfigFragment {
+ public:
+  virtual ~ConfigFragment();
+
+  virtual std::string Name() const = 0;
+  virtual Json::Value Serialize() const = 0;
+  virtual bool Deserialize(const Json::Value&) = 0;
+};
 
 }  // namespace cuttlefish
diff --git a/host/libs/config/custom_actions.cpp b/host/libs/config/custom_actions.cpp
index 2c78eba..1cc2e06 100644
--- a/host/libs/config/custom_actions.cpp
+++ b/host/libs/config/custom_actions.cpp
@@ -29,6 +29,9 @@
 
 const char* kCustomActionShellCommand = "shell_command";
 const char* kCustomActionServer = "server";
+const char* kCustomActionDeviceStates = "device_states";
+const char* kCustomActionDeviceStateLidSwitchOpen = "lid_switch_open";
+const char* kCustomActionDeviceStateHingeAngleValue = "hinge_angle_value";
 const char* kCustomActionButton = "button";
 const char* kCustomActionButtons = "buttons";
 const char* kCustomActionButtonCommand = "command";
@@ -39,11 +42,15 @@
 
 
 CustomActionConfig::CustomActionConfig(const Json::Value& dictionary) {
+  if (dictionary.isMember(kCustomActionShellCommand) +
+          dictionary.isMember(kCustomActionServer) +
+          dictionary.isMember(kCustomActionDeviceStates) !=
+      1) {
+    LOG(FATAL) << "Custom action must contain exactly one of shell_command, "
+               << "server, or device_states";
+    return;
+  }
   if (dictionary.isMember(kCustomActionShellCommand)) {
-    if (dictionary.isMember(kCustomActionServer)) {
-      LOG(ERROR) << "Custom action contains both shell command and action server.";
-      return;
-    }
     // Shell command with one button.
     Json::Value button_entry = dictionary[kCustomActionButton];
     buttons = {{button_entry[kCustomActionButtonCommand].asString(),
@@ -60,8 +67,29 @@
       buttons.push_back(button);
     }
     server = dictionary[kCustomActionServer].asString();
+  } else if (dictionary.isMember(kCustomActionDeviceStates)) {
+    // Device state(s) with one button.
+    // Each button press cycles to the next state, then repeats to the first.
+    Json::Value button_entry = dictionary[kCustomActionButton];
+    buttons = {{button_entry[kCustomActionButtonCommand].asString(),
+                button_entry[kCustomActionButtonTitle].asString(),
+                button_entry[kCustomActionButtonIconName].asString()}};
+    for (const Json::Value& device_state_entry :
+         dictionary[kCustomActionDeviceStates]) {
+      DeviceState state;
+      if (device_state_entry.isMember(kCustomActionDeviceStateLidSwitchOpen)) {
+        state.lid_switch_open =
+            device_state_entry[kCustomActionDeviceStateLidSwitchOpen].asBool();
+      }
+      if (device_state_entry.isMember(
+              kCustomActionDeviceStateHingeAngleValue)) {
+        state.hinge_angle_value =
+            device_state_entry[kCustomActionDeviceStateHingeAngleValue].asInt();
+      }
+      device_states.push_back(state);
+    }
   } else {
-    LOG(ERROR) << "Unknown custom action format.";
+    LOG(FATAL) << "Unknown custom action type.";
   }
 }
 
@@ -88,8 +116,30 @@
       button_entry[kCustomActionButtonIconName] = button.icon_name;
       custom_action[kCustomActionButtons].append(button_entry);
     }
+  } else if (!device_states.empty()) {
+    // Device state(s) with one button.
+    custom_action[kCustomActionDeviceStates] = Json::Value(Json::arrayValue);
+    for (const auto& device_state : device_states) {
+      Json::Value device_state_entry;
+      if (device_state.lid_switch_open) {
+        device_state_entry[kCustomActionDeviceStateLidSwitchOpen] =
+            *device_state.lid_switch_open;
+      }
+      if (device_state.hinge_angle_value) {
+        device_state_entry[kCustomActionDeviceStateHingeAngleValue] =
+            *device_state.hinge_angle_value;
+      }
+      custom_action[kCustomActionDeviceStates].append(device_state_entry);
+    }
+    custom_action[kCustomActionButton] = Json::Value();
+    custom_action[kCustomActionButton][kCustomActionButtonCommand] =
+        buttons[0].command;
+    custom_action[kCustomActionButton][kCustomActionButtonTitle] =
+        buttons[0].title;
+    custom_action[kCustomActionButton][kCustomActionButtonIconName] =
+        buttons[0].icon_name;
   } else {
-    LOG(ERROR) << "Unknown custom action type.";
+    LOG(FATAL) << "Unknown custom action type.";
   }
   return custom_action;
 }
diff --git a/host/libs/config/custom_actions.h b/host/libs/config/custom_actions.h
index 51e73ba..6279401 100644
--- a/host/libs/config/custom_actions.h
+++ b/host/libs/config/custom_actions.h
@@ -29,6 +29,11 @@
   std::string icon_name;
 };
 
+struct DeviceState {
+  std::optional<bool> lid_switch_open;
+  std::optional<int> hinge_angle_value;
+};
+
 struct CustomActionConfig {
   CustomActionConfig(const Json::Value&);
   Json::Value ToJson() const;
@@ -36,6 +41,7 @@
   std::vector<ControlPanelButton> buttons;
   std::optional<std::string> shell_command;
   std::optional<std::string> server;
+  std::vector<DeviceState> device_states;
 };
 
 }  // namespace cuttlefish
diff --git a/host/libs/config/cuttlefish_config.cpp b/host/libs/config/cuttlefish_config.cpp
index b9b04e5..6a7a5f0 100644
--- a/host/libs/config/cuttlefish_config.cpp
+++ b/host/libs/config/cuttlefish_config.cpp
@@ -87,6 +87,31 @@
   return StringFromEnv(environment_key, default_value) + "/" + subpath;
 }
 
+ConfigFragment::~ConfigFragment() = default;
+
+static constexpr char kFragments[] = "fragments";
+bool CuttlefishConfig::LoadFragment(ConfigFragment& fragment) const {
+  if (!dictionary_->isMember(kFragments)) {
+    LOG(ERROR) << "Fragments member was missing";
+    return false;
+  }
+  const Json::Value& json_fragments = (*dictionary_)[kFragments];
+  if (!json_fragments.isMember(fragment.Name())) {
+    LOG(ERROR) << "Could not find a fragment called " << fragment.Name();
+    return false;
+  }
+  return fragment.Deserialize(json_fragments[fragment.Name()]);
+}
+bool CuttlefishConfig::SaveFragment(const ConfigFragment& fragment) {
+  Json::Value& json_fragments = (*dictionary_)[kFragments];
+  if (json_fragments.isMember(fragment.Name())) {
+    LOG(ERROR) << "Already have a fragment called " << fragment.Name();
+    return false;
+  }
+  json_fragments[fragment.Name()] = fragment.Serialize();
+  return true;
+}
+
 static constexpr char kAssemblyDir[] = "assembly_dir";
 std::string CuttlefishConfig::assembly_dir() const {
   return (*dictionary_)[kAssemblyDir].asString();
@@ -123,13 +148,11 @@
   (*dictionary_)[kMemoryMb] = memory_mb;
 }
 
-static constexpr char kDpi[] = "dpi";
-int CuttlefishConfig::dpi() const { return (*dictionary_)[kDpi].asInt(); }
-void CuttlefishConfig::set_dpi(int dpi) { (*dictionary_)[kDpi] = dpi; }
-
 static constexpr char kDisplayConfigs[] = "display_configs";
 static constexpr char kXRes[] = "x_res";
 static constexpr char kYRes[] = "y_res";
+static constexpr char kDpi[] = "dpi";
+static constexpr char kRefreshRateHz[] = "refresh_rate_hz";
 std::vector<CuttlefishConfig::DisplayConfig>
 CuttlefishConfig::display_configs() const {
   std::vector<DisplayConfig> display_configs;
@@ -137,6 +160,9 @@
     DisplayConfig display_config = {};
     display_config.width = display_config_json[kXRes].asInt();
     display_config.height = display_config_json[kYRes].asInt();
+    display_config.dpi = display_config_json[kDpi].asInt();
+    display_config.refresh_rate_hz =
+        display_config_json[kRefreshRateHz].asInt();
     display_configs.emplace_back(std::move(display_config));
   }
   return display_configs;
@@ -149,20 +175,14 @@
     Json::Value display_config_json(Json::objectValue);
     display_config_json[kXRes] = display_configs.width;
     display_config_json[kYRes] = display_configs.height;
+    display_config_json[kDpi] = display_configs.dpi;
+    display_config_json[kRefreshRateHz] = display_configs.refresh_rate_hz;
     display_configs_json.append(display_config_json);
   }
 
   (*dictionary_)[kDisplayConfigs] = display_configs_json;
 }
 
-static constexpr char kRefreshRateHz[] = "refresh_rate_hz";
-int CuttlefishConfig::refresh_rate_hz() const {
-  return (*dictionary_)[kRefreshRateHz].asInt();
-}
-void CuttlefishConfig::set_refresh_rate_hz(int refresh_rate_hz) {
-  (*dictionary_)[kRefreshRateHz] = refresh_rate_hz;
-}
-
 void CuttlefishConfig::SetPath(const std::string& key,
                                const std::string& path) {
   if (!path.empty()) {
@@ -195,35 +215,6 @@
   return (*dictionary_)[kCuttlefishEnvPath].asString();
 }
 
-static AdbMode stringToAdbMode(std::string mode) {
-  std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower);
-  if (mode == "vsock_tunnel") {
-    return AdbMode::VsockTunnel;
-  } else if (mode == "vsock_half_tunnel") {
-    return AdbMode::VsockHalfTunnel;
-  } else if (mode == "native_vsock") {
-    return AdbMode::NativeVsock;
-  } else {
-    return AdbMode::Unknown;
-  }
-}
-
-static constexpr char kAdbMode[] = "adb_mode";
-std::set<AdbMode> CuttlefishConfig::adb_mode() const {
-  std::set<AdbMode> args_set;
-  for (auto& mode : (*dictionary_)[kAdbMode]) {
-    args_set.insert(stringToAdbMode(mode.asString()));
-  }
-  return args_set;
-}
-void CuttlefishConfig::set_adb_mode(const std::set<std::string>& mode) {
-  Json::Value mode_json_obj(Json::arrayValue);
-  for (const auto& arg : mode) {
-    mode_json_obj.append(arg);
-  }
-  (*dictionary_)[kAdbMode] = mode_json_obj;
-}
-
 static SecureHal StringToSecureHal(std::string mode) {
   std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower);
   if (mode == "keymint") {
@@ -335,14 +326,6 @@
   return (*dictionary_)[kEnableVehicleHalServer].asBool();
 }
 
-static constexpr char kVehicleHalServerBinary[] = "vehicle_hal_server_binary";
-void CuttlefishConfig::set_vehicle_hal_grpc_server_binary(const std::string& vehicle_hal_server_binary) {
-  (*dictionary_)[kVehicleHalServerBinary] = vehicle_hal_server_binary;
-}
-std::string CuttlefishConfig::vehicle_hal_grpc_server_binary() const {
-  return (*dictionary_)[kVehicleHalServerBinary].asString();
-}
-
 static constexpr char kCustomActions[] = "custom_actions";
 void CuttlefishConfig::set_custom_actions(const std::vector<CustomActionConfig>& actions) {
   Json::Value actions_array(Json::arrayValue);
@@ -384,14 +367,6 @@
   (*dictionary_)[kRestartSubprocesses] = restart_subprocesses;
 }
 
-static constexpr char kRunAdbConnector[] = "run_adb_connector";
-bool CuttlefishConfig::run_adb_connector() const {
-  return (*dictionary_)[kRunAdbConnector].asBool();
-}
-void CuttlefishConfig::set_run_adb_connector(bool run_adb_connector) {
-  (*dictionary_)[kRunAdbConnector] = run_adb_connector;
-}
-
 static constexpr char kRunAsDaemon[] = "run_as_daemon";
 bool CuttlefishConfig::run_as_daemon() const {
   return (*dictionary_)[kRunAsDaemon].asBool();
@@ -503,6 +478,14 @@
   return (*dictionary_)[kSigServerPath].asString();
 }
 
+static constexpr char kSigServerSecure[] = "webrtc_sig_server_secure";
+void CuttlefishConfig::set_sig_server_secure(bool secure) {
+  (*dictionary_)[kSigServerSecure] = secure;
+}
+bool CuttlefishConfig::sig_server_secure() const {
+  return (*dictionary_)[kSigServerSecure].asBool();
+}
+
 static constexpr char kSigServerStrict[] = "webrtc_sig_server_strict";
 void CuttlefishConfig::set_sig_server_strict(bool strict) {
   (*dictionary_)[kSigServerStrict] = strict;
@@ -695,6 +678,30 @@
   return (*dictionary_)[kVhostNet].asBool();
 }
 
+static constexpr char kVhostUserMac80211Hwsim[] = "vhost_user_mac80211_hwsim";
+void CuttlefishConfig::set_vhost_user_mac80211_hwsim(const std::string& path) {
+  (*dictionary_)[kVhostUserMac80211Hwsim] = path;
+}
+std::string CuttlefishConfig::vhost_user_mac80211_hwsim() const {
+  return (*dictionary_)[kVhostUserMac80211Hwsim].asString();
+}
+
+static constexpr char kApRootfsImage[] = "ap_rootfs_image";
+std::string CuttlefishConfig::ap_rootfs_image() const {
+  return (*dictionary_)[kApRootfsImage].asString();
+}
+void CuttlefishConfig::set_ap_rootfs_image(const std::string& ap_rootfs_image) {
+  (*dictionary_)[kApRootfsImage] = ap_rootfs_image;
+}
+
+static constexpr char kApKernelImage[] = "ap_kernel_image";
+std::string CuttlefishConfig::ap_kernel_image() const {
+  return (*dictionary_)[kApKernelImage].asString();
+}
+void CuttlefishConfig::set_ap_kernel_image(const std::string& ap_kernel_image) {
+  (*dictionary_)[kApKernelImage] = ap_kernel_image;
+}
+
 static constexpr char kEthernet[] = "ethernet";
 void CuttlefishConfig::set_ethernet(bool ethernet) {
   (*dictionary_)[kEthernet] = ethernet;
@@ -818,6 +825,10 @@
   return AbsolutePath(assembly_dir() + "/" + file_name);
 }
 
+std::string CuttlefishConfig::os_composite_disk_path() const {
+  return AssemblyPath("os_composite.img");
+}
+
 CuttlefishConfig::MutableInstanceSpecific CuttlefishConfig::ForInstance(int num) {
   return MutableInstanceSpecific(this, std::to_string(num));
 }
@@ -876,11 +887,6 @@
   return prefix + str;
 }
 
-int GetDefaultPerInstanceVsockCid() {
-  constexpr int kFirstGuestCid = 3;
-  return HostSupportsVsock() ? ForCurrentInstance(kFirstGuestCid) : 0;
-}
-
 std::string DefaultHostArtifactsPath(const std::string& file_name) {
   return (StringFromEnv("ANDROID_SOONG_HOST_OUT", StringFromEnv("HOME", ".")) + "/") +
          file_name;
@@ -906,10 +912,4 @@
   return supported;
 }
 
-bool HostSupportsVsock() {
-  static bool supported =
-      std::system(
-          "/usr/lib/cuttlefish-common/bin/capability_query.py vsock") == 0;
-  return supported;
-}
 }  // namespace cuttlefish
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index 2aafc84..2dcd043 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include "common/libs/utils/environment.h"
+#include "host/libs/config/config_fragment.h"
 #include "host/libs/config/custom_actions.h"
 
 namespace Json {
@@ -35,9 +36,6 @@
 namespace cuttlefish {
 constexpr char kLogcatSerialMode[] = "serial";
 constexpr char kLogcatVsockMode[] = "vsock";
-}
-
-namespace cuttlefish {
 
 constexpr char kDefaultUuidPrefix[] = "699acfc4-c8c4-11e7-882b-5065f31dc1";
 constexpr char kCuttlefishConfigEnvVarName[] = "CUTTLEFISH_CONFIG_FILE";
@@ -55,13 +53,7 @@
 constexpr char kInternalDirName[] = "internal";
 constexpr char kSharedDirName[] = "shared";
 constexpr char kCrosvmVarEmptyDir[] = "/var/empty";
-
-enum class AdbMode {
-  VsockTunnel,
-  VsockHalfTunnel,
-  NativeVsock,
-  Unknown,
-};
+constexpr char kKernelLoadedMessage[] = "] Linux version";
 
 enum class SecureHal {
   Unknown,
@@ -84,11 +76,16 @@
   // processes by passing the --config_file option.
   bool SaveToFile(const std::string& file) const;
 
+  bool SaveFragment(const ConfigFragment&);
+  bool LoadFragment(ConfigFragment&) const;
+
   std::string assembly_dir() const;
   void set_assembly_dir(const std::string& assembly_dir);
 
   std::string AssemblyPath(const std::string&) const;
 
+  std::string os_composite_disk_path() const;
+
   std::string vm_manager() const;
   void set_vm_manager(const std::string& name);
 
@@ -101,15 +98,11 @@
   int memory_mb() const;
   void set_memory_mb(int memory_mb);
 
-  int dpi() const;
-  void set_dpi(int dpi);
-
-  int refresh_rate_hz() const;
-  void set_refresh_rate_hz(int refresh_rate_hz);
-
   struct DisplayConfig {
     int width;
     int height;
+    int dpi;
+    int refresh_rate_hz;
   };
 
   std::vector<DisplayConfig> display_configs() const;
@@ -124,9 +117,6 @@
   void set_cuttlefish_env_path(const std::string& path);
   std::string cuttlefish_env_path() const;
 
-  void set_adb_mode(const std::set<std::string>& modes);
-  std::set<AdbMode> adb_mode() const;
-
   void set_secure_hals(const std::set<std::string>& hals);
   std::set<SecureHal> secure_hals() const;
 
@@ -163,18 +153,12 @@
   void set_enable_vehicle_hal_grpc_server(bool enable_vhal_server);
   bool enable_vehicle_hal_grpc_server() const;
 
-  void set_vehicle_hal_grpc_server_binary(const std::string& vhal_server_binary);
-  std::string vehicle_hal_grpc_server_binary() const;
-
   void set_custom_actions(const std::vector<CustomActionConfig>& actions);
   std::vector<CustomActionConfig> custom_actions() const;
 
   void set_restart_subprocesses(bool restart_subprocesses);
   bool restart_subprocesses() const;
 
-  void set_run_adb_connector(bool run_adb_connector);
-  bool run_adb_connector() const;
-
   void set_enable_gnss_grpc_proxy(const bool enable_gnss_grpc_proxy);
   bool enable_gnss_grpc_proxy() const;
 
@@ -252,6 +236,11 @@
   void set_sig_server_path(const std::string& path);
   std::string sig_server_path() const;
 
+  // Whether the webrtc process should use a secure connection (WSS) to the
+  // signaling server.
+  void set_sig_server_secure(bool secure);
+  bool sig_server_secure() const;
+
   // Whether the webrtc process should attempt to verify the authenticity of the
   // signaling server (reject self signed certificates)
   void set_sig_server_strict(bool strict);
@@ -294,6 +283,15 @@
   void set_vhost_net(bool vhost_net);
   bool vhost_net() const;
 
+  void set_vhost_user_mac80211_hwsim(const std::string& path);
+  std::string vhost_user_mac80211_hwsim() const;
+
+  void set_ap_rootfs_image(const std::string& path);
+  std::string ap_rootfs_image() const;
+
+  void set_ap_kernel_image(const std::string& path);
+  std::string ap_kernel_image() const;
+
   void set_ethernet(bool ethernet);
   bool ethernet() const;
 
@@ -362,7 +360,9 @@
     // Port number to connect to the audiocontrol server on the guest
     int audiocontrol_server_port() const;
     // Port number to connect to the adb server on the host
-    int host_port() const;
+    int adb_host_port() const;
+    // Device-specific ID to distinguish modem simulators. Must be 4 digits.
+    int modem_simulator_host_id() const;
     // Port number to connect to the gnss grpc proxy server on the host
     int gnss_grpc_proxy_server_port() const;
     std::string adb_ip_and_port() const;
@@ -370,6 +370,8 @@
     int rootcanal_hci_port() const;
     int rootcanal_link_port() const;
     int rootcanal_test_port() const;
+    // Port number to connect to the camera hal on the guest
+    int camera_server_port() const;
     std::string rootcanal_config_file() const;
     std::string rootcanal_default_commands_file() const;
 
@@ -396,13 +398,12 @@
 
     std::string instance_internal_dir() const;
 
-    std::string touch_socket_path() const;
+    std::string touch_socket_path(int screen_idx) const;
     std::string keyboard_socket_path() const;
     std::string switches_socket_path() const;
     std::string frames_socket_path() const;
 
-    // mock hal guest socket that will be vsock/virtio later on
-    std::string confui_hal_guest_socket_path() const;
+    int confui_host_vsock_port() const;
 
     std::string access_kregistry_path() const;
 
@@ -430,14 +431,10 @@
 
     std::string sdcard_path() const;
 
-    std::string os_composite_disk_path() const;
-
     std::string persistent_composite_disk_path() const;
 
     std::string uboot_env_image_path() const;
 
-    std::string vendor_boot_image_path() const;
-
     std::string audio_server_path() const;
 
     // modem simulator related
@@ -451,9 +448,11 @@
     bool start_webrtc_sig_server() const;
 
     // Wifi MAC address inside the guest
-    std::array<unsigned char, 6> wifi_mac_address() const;
+    int wifi_mac_prefix() const;
 
     std::string factory_reset_protected_path() const;
+
+    std::string persistent_bootconfig_path() const;
   };
 
   // A view into an existing CuttlefishConfig object for a particular instance.
@@ -478,11 +477,14 @@
     void set_keymaster_vsock_port(int keymaster_vsock_port);
     void set_vehicle_hal_server_port(int vehicle_server_port);
     void set_audiocontrol_server_port(int audiocontrol_server_port);
-    void set_host_port(int host_port);
+    void set_adb_host_port(int adb_host_port);
+    void set_modem_simulator_host_id(int modem_simulator_id);
     void set_adb_ip_and_port(const std::string& ip_port);
+    void set_confui_host_vsock_port(int confui_host_port);
     void set_rootcanal_hci_port(int rootcanal_hci_port);
     void set_rootcanal_link_port(int rootcanal_link_port);
     void set_rootcanal_test_port(int rootcanal_test_port);
+    void set_camera_server_port(int camera_server_port);
     void set_rootcanal_config_file(const std::string& rootcanal_config_file);
     void set_rootcanal_default_commands_file(
         const std::string& rootcanal_default_commands_file);
@@ -502,7 +504,7 @@
     void set_webrtc_device_id(const std::string& id);
     void set_start_webrtc_signaling_server(bool start);
     // Wifi MAC address inside the guest
-    void set_wifi_mac_address(const std::array<unsigned char, 6>&);
+    void set_wifi_mac_prefix(const int wifi_mac_prefix);
     // Gnss grpc proxy server port inside the host
     void set_gnss_grpc_proxy_server_port(int gnss_grpc_proxy_server_port);
     // Gnss grpc proxy local file path
@@ -546,9 +548,6 @@
 // Returns a random serial number appeneded to a given prefix.
 std::string RandomSerialNumber(const std::string& prefix);
 
-std::string GetDefaultMempath();
-int GetDefaultPerInstanceVsockCid();
-
 std::string DefaultHostArtifactsPath(const std::string& file);
 std::string HostBinaryPath(const std::string& file);
 std::string DefaultGuestImagePath(const std::string& file);
@@ -558,7 +557,6 @@
 
 // Whether the host supports qemu
 bool HostSupportsQemuCli();
-bool HostSupportsVsock();
 
 // GPU modes
 extern const char* const kGpuModeAuto;
diff --git a/host/libs/config/cuttlefish_config_instance.cpp b/host/libs/config/cuttlefish_config_instance.cpp
index 9bd8c8a..81cf7db 100644
--- a/host/libs/config/cuttlefish_config_instance.cpp
+++ b/host/libs/config/cuttlefish_config_instance.cpp
@@ -165,10 +165,6 @@
   return AbsolutePath(PerInstancePath("sdcard.img"));
 }
 
-std::string CuttlefishConfig::InstanceSpecific::os_composite_disk_path() const {
-  return AbsolutePath(PerInstancePath("os_composite.img"));
-}
-
 std::string CuttlefishConfig::InstanceSpecific::persistent_composite_disk_path()
     const {
   return AbsolutePath(PerInstancePath("persistent_composite.img"));
@@ -178,10 +174,6 @@
   return AbsolutePath(PerInstancePath("uboot_env.img"));
 }
 
-std::string CuttlefishConfig::InstanceSpecific::vendor_boot_image_path() const {
-  return AbsolutePath(PerInstancePath("vendor_boot_repacked.img"));
-}
-
 static constexpr char kMobileBridgeName[] = "mobile_bridge_name";
 
 std::string CuttlefishConfig::InstanceSpecific::audio_server_path() const {
@@ -205,9 +197,14 @@
   (*Dictionary())[kMobileTapName] = mobile_tap_name;
 }
 
-std::string CuttlefishConfig::InstanceSpecific::confui_hal_guest_socket_path()
-    const {
-  return PerInstanceInternalPath("confui_mock_hal_guest.sock");
+static constexpr char kConfUiHostPort[] = "confirmation_ui_host_port";
+int CuttlefishConfig::InstanceSpecific::confui_host_vsock_port() const {
+  return (*Dictionary())[kConfUiHostPort].asInt();
+}
+
+void CuttlefishConfig::MutableInstanceSpecific::set_confui_host_vsock_port(
+    int port) {
+  (*Dictionary())[kConfUiHostPort] = port;
 }
 
 static constexpr char kWifiTapName[] = "wifi_tap_name";
@@ -263,12 +260,21 @@
   (*Dictionary())[kUuid] = uuid;
 }
 
-static constexpr char kHostPort[] = "host_port";
-int CuttlefishConfig::InstanceSpecific::host_port() const {
+static constexpr char kHostPort[] = "adb_host_port";
+int CuttlefishConfig::InstanceSpecific::adb_host_port() const {
   return (*Dictionary())[kHostPort].asInt();
 }
-void CuttlefishConfig::MutableInstanceSpecific::set_host_port(int host_port) {
-  (*Dictionary())[kHostPort] = host_port;
+void CuttlefishConfig::MutableInstanceSpecific::set_adb_host_port(int port) {
+  (*Dictionary())[kHostPort] = port;
+}
+
+static constexpr char kModemSimulatorId[] = "modem_simulator_host_id";
+int CuttlefishConfig::InstanceSpecific::modem_simulator_host_id() const {
+  return (*Dictionary())[kModemSimulatorId].asInt();
+}
+void CuttlefishConfig::MutableInstanceSpecific::set_modem_simulator_host_id(
+    int id) {
+  (*Dictionary())[kModemSimulatorId] = id;
 }
 
 static constexpr char kAdbIPAndPort[] = "adb_ip_and_port";
@@ -389,6 +395,15 @@
   (*Dictionary())[kRootcanalTestPort] = rootcanal_test_port;
 }
 
+static constexpr char kCameraServerPort[] = "camera_server_port";
+int CuttlefishConfig::InstanceSpecific::camera_server_port() const {
+  return (*Dictionary())[kCameraServerPort].asInt();
+}
+void CuttlefishConfig::MutableInstanceSpecific::set_camera_server_port(
+    int camera_server_port) {
+  (*Dictionary())[kCameraServerPort] = camera_server_port;
+}
+
 static constexpr char kRootcanalConfigFile[] = "rootcanal_config_file";
 std::string CuttlefishConfig::InstanceSpecific::rootcanal_config_file() const {
   return (*Dictionary())[kRootcanalConfigFile].asString();
@@ -429,8 +444,10 @@
   return (*Dictionary())[kStartSigServer].asBool();
 }
 
-std::string CuttlefishConfig::InstanceSpecific::touch_socket_path() const {
-  return PerInstanceInternalPath("touch.sock");
+std::string CuttlefishConfig::InstanceSpecific::touch_socket_path(
+    int screen_idx) const {
+  return PerInstanceInternalPath(
+      ("touch_" + std::to_string(screen_idx) + ".sock").c_str());
 }
 
 std::string CuttlefishConfig::InstanceSpecific::keyboard_socket_path() const {
@@ -445,32 +462,24 @@
   return PerInstanceInternalPath("frames.sock");
 }
 
-static constexpr char kWifiMacAddress[] = "wifi_mac_address";
-void CuttlefishConfig::MutableInstanceSpecific::set_wifi_mac_address(
-    const std::array<unsigned char, 6>& mac_address) {
-  Json::Value mac_address_obj(Json::arrayValue);
-  for (const auto& num : mac_address) {
-    mac_address_obj.append(num);
-  }
-  (*Dictionary())[kWifiMacAddress] = mac_address_obj;
+static constexpr char kWifiMacPrefix[] = "wifi_mac_prefix";
+int CuttlefishConfig::InstanceSpecific::wifi_mac_prefix() const {
+  return (*Dictionary())[kWifiMacPrefix].asInt();
 }
-std::array<unsigned char, 6> CuttlefishConfig::InstanceSpecific::wifi_mac_address() const {
-  std::array<unsigned char, 6> mac_address{0, 0, 0, 0, 0, 0};
-  auto mac_address_obj = (*Dictionary())[kWifiMacAddress];
-  if (mac_address_obj.size() != 6) {
-    LOG(ERROR) << kWifiMacAddress << " entry had wrong size";
-    return {};
-  }
-  for (int i = 0; i < 6; i++) {
-    mac_address[i] = mac_address_obj[i].asInt();
-  }
-  return mac_address;
+void CuttlefishConfig::MutableInstanceSpecific::set_wifi_mac_prefix(
+    int wifi_mac_prefix) {
+  (*Dictionary())[kWifiMacPrefix] = wifi_mac_prefix;
 }
 
 std::string CuttlefishConfig::InstanceSpecific::factory_reset_protected_path() const {
   return PerInstanceInternalPath("factory_reset_protected.img");
 }
 
+std::string CuttlefishConfig::InstanceSpecific::persistent_bootconfig_path()
+    const {
+  return PerInstanceInternalPath("bootconfig");
+}
+
 std::string CuttlefishConfig::InstanceSpecific::PerInstancePath(
     const char* file_name) const {
   return (instance_dir() + "/") + file_name;
diff --git a/host/libs/config/data_image.cpp b/host/libs/config/data_image.cpp
index fbfc370..189433f 100644
--- a/host/libs/config/data_image.cpp
+++ b/host/libs/config/data_image.cpp
@@ -134,11 +134,13 @@
     execute({"/sbin/mkfs.ext4", image});
   } else if (image_fmt == "f2fs") {
     auto make_f2fs_path = cuttlefish::HostBinaryPath("make_f2fs");
-    execute({make_f2fs_path, "-t", image_fmt, image, "-C", "utf8",
-            "-O", "compression,extra_attr", "-g", "android"});
+    execute({make_f2fs_path, "-t", image_fmt, image, "-C", "utf8", "-O",
+             "compression,extra_attr,prjquota", "-g", "android"});
   } else if (image_fmt == "sdcard") {
     // Reserve 1MB in the image for the MBR and padding, to simulate what
     // other OSes do by default when partitioning a drive
+    off_t offset_size_bytes = 1 << 20;
+    image_size_bytes -= offset_size_bytes;
     CHECK(NewfsMsdos(image, num_mb, 1) == true)
         << "Failed to create SD-Card filesystem";
     // Write the MBR after the filesystem is formatted, as the formatting tools
@@ -146,8 +148,8 @@
     MasterBootRecord mbr = {
         .partitions = {{
             .partition_type = 0xC,
-            .first_lba = (std::uint32_t)1 << 20 / SECTOR_SIZE,
-            .num_sectors = (std::uint32_t)image_size_bytes / SECTOR_SIZE,
+            .first_lba = (std::uint32_t) offset_size_bytes / SECTOR_SIZE,
+            .num_sectors = (std::uint32_t) image_size_bytes / SECTOR_SIZE,
         }},
         .boot_signature = {0x55, 0xAA},
     };
diff --git a/host/libs/config/feature.cpp b/host/libs/config/feature.cpp
new file mode 100644
index 0000000..3604fb4
--- /dev/null
+++ b/host/libs/config/feature.cpp
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+#include "host/libs/config/feature.h"
+
+#include <unordered_set>
+
+namespace cuttlefish {
+
+Feature::~Feature() {}
+
+/* static */ bool Feature::RunSetup(const std::vector<Feature*>& features) {
+  std::unordered_set<Feature*> enabled;
+  for (const auto& feature : features) {
+    CHECK(feature != nullptr) << "Received null feature";
+    if (feature->Enabled()) {
+      enabled.insert(feature);
+    }
+  }
+  // Collect these in a vector first to trigger any obvious dependency issues.
+  std::vector<Feature*> ordered_features;
+  auto add_feature = [&ordered_features](Feature* feature) -> bool {
+    ordered_features.push_back(feature);
+    return true;
+  };
+  if (!FeatureSuperclass<Feature>::TopologicalVisit(enabled, add_feature)) {
+    LOG(ERROR) << "Dependency issue detected, not performing any setup.";
+    return false;
+  }
+  // TODO(b/189153501): This can potentially be parallelized.
+  for (auto& feature : ordered_features) {
+    LOG(DEBUG) << "Running setup for " << feature->Name();
+    if (!feature->Setup()) {
+      LOG(ERROR) << "Setup failed for " << feature->Name();
+      return false;
+    }
+  }
+  return true;
+}
+
+bool FlagFeature::ProcessFlags(const std::vector<FlagFeature*>& features,
+                               std::vector<std::string>& flags) {
+  std::unordered_set<FlagFeature*> features_set(features.begin(),
+                                                features.end());
+  if (features_set.count(nullptr)) {
+    LOG(ERROR) << "Received null feature";
+    return false;
+  }
+  auto handle = [&flags](FlagFeature* feature) -> bool {
+    return feature->Process(flags);
+  };
+  if (!FeatureSuperclass<FlagFeature>::TopologicalVisit(features_set, handle)) {
+    LOG(ERROR) << "Unable to parse flags.";
+    return false;
+  }
+  return true;
+}
+
+bool FlagFeature::WriteGflagsHelpXml(const std::vector<FlagFeature*>& features,
+                                     std::ostream& out) {
+  // Lifted from external/gflags/src/gflags_reporting.cc:ShowXMLOfFlags
+  out << "<?xml version=\"1.0\"?>\n";
+  out << "<AllFlags>\n";
+  out << "  <program>program</program>\n";
+  out << "  <usage>usage</usage>\n";
+  for (const auto& feature : features) {
+    if (!feature) {
+      LOG(ERROR) << "Received null feature";
+      return false;
+    }
+    if (!feature->WriteGflagsCompatHelpXml(out)) {
+      LOG(ERROR) << "Failure to write xml";
+      return false;
+    }
+  }
+  out << "</AllFlags>";
+  return true;
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/feature.h b/host/libs/config/feature.h
new file mode 100644
index 0000000..59564c0
--- /dev/null
+++ b/host/libs/config/feature.h
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <android-base/logging.h>
+#include <ostream>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace cuttlefish {
+
+// TODO(schuffelen): Rename this "Feature"
+template <typename Subclass>
+class FeatureSuperclass {
+ public:
+  virtual ~FeatureSuperclass() = default;
+
+  virtual std::string Name() const = 0;
+  virtual std::unordered_set<Subclass*> Dependencies() const = 0;
+
+  static bool TopologicalVisit(const std::unordered_set<Subclass*>& features,
+                               const std::function<bool(Subclass*)>& callback);
+};
+
+// TODO(schuffelen): Rename this "SetupFeature"
+class Feature : public virtual FeatureSuperclass<Feature> {
+ public:
+  virtual ~Feature();
+
+  static bool RunSetup(const std::vector<Feature*>& features);
+
+  virtual bool Enabled() const = 0;
+
+ protected:
+  virtual bool Setup() = 0;
+};
+
+class FlagFeature : public FeatureSuperclass<FlagFeature> {
+ public:
+  static bool ProcessFlags(const std::vector<FlagFeature*>& features,
+                           std::vector<std::string>& flags);
+  static bool WriteGflagsHelpXml(const std::vector<FlagFeature*>& features,
+                                 std::ostream& out);
+
+ protected:
+  // Must be executed in dependency order following Dependencies(). Expected to
+  // mutate the `flags` argument to remove handled flags, and possibly introduce
+  // new flag values (e.g. from a file).
+  virtual bool Process(std::vector<std::string>& flags) = 0;
+
+  // TODO(schuffelen): Migrate the xml help to human-readable help output after
+  // the gflags migration is done.
+
+  // Write an xml fragment that is compatible with gflags' `--helpxml` format.
+  virtual bool WriteGflagsCompatHelpXml(std::ostream& out) const = 0;
+};
+
+template <typename Subclass>
+bool FeatureSuperclass<Subclass>::TopologicalVisit(
+    const std::unordered_set<Subclass*>& features,
+    const std::function<bool(Subclass*)>& callback) {
+  enum class Status { UNVISITED, VISITING, VISITED };
+  std::unordered_map<Subclass*, Status> features_status;
+  for (const auto& feature : features) {
+    features_status[feature] = Status::UNVISITED;
+  }
+  std::function<bool(Subclass*)> visit;
+  visit = [&callback, &features_status, &visit](Subclass* feature) -> bool {
+    if (features_status.count(feature) == 0) {
+      LOG(ERROR) << "Dependency edge to " << feature->Name() << " but it is not"
+                 << " part of the feature graph. This feature is either "
+                 << "disabled or not correctly registered.";
+      return false;
+    } else if (features_status[feature] == Status::VISITED) {
+      return true;
+    } else if (features_status[feature] == Status::VISITING) {
+      LOG(ERROR) << "Cycle detected while visiting " << feature->Name();
+      return false;
+    }
+    features_status[feature] = Status::VISITING;
+    for (const auto& dependency : feature->Dependencies()) {
+      CHECK(dependency != nullptr)
+          << "Feature " << feature->Name() << " has a null dependency.";
+      if (!visit(dependency)) {
+        LOG(ERROR) << "Error detected while visiting " << feature->Name();
+        return false;
+      }
+    }
+    features_status[feature] = Status::VISITED;
+    if (!callback(feature)) {
+      LOG(ERROR) << "Callback error on " << feature->Name();
+      return false;
+    }
+    return true;
+  };
+  for (const auto& feature : features) {
+    if (!visit(feature)) {  // `visit` will log the error chain.
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/inject.h b/host/libs/config/inject.h
new file mode 100644
index 0000000..4e2a6e0
--- /dev/null
+++ b/host/libs/config/inject.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <fruit/fruit.h>
+#include <type_traits>
+
+namespace cuttlefish {
+
+/**
+ * This is a template helper to add bindings for a set of implementation
+ * classes that may each be part of multiple multibindings. To be more specific,
+ * for these example classes:
+ *
+ *   class ImplementationA : public IntX, IntY {};
+ *   class ImplementationB : public IntY, IntZ {};
+ *
+ * can be installed with
+ *
+ *   using Deps = fruit::Required<...>;
+ *   using Bases = Multibindings<Deps>::Bases<IntX, IntY, IntZ>;
+ *   return fruit::createComponent()
+ *     .install(Bases::Impls<ImplementationA, ImplementationB>);
+ *
+ * Note that not all implementations have to implement all interfaces. Invalid
+ * combinations are filtered out at compile-time through SFINAE.
+ */
+template <typename Deps>
+struct Multibindings {
+  /* SFINAE logic for an individual interface binding. The class does implement
+   * the interface, so add a multibinding. */
+  template <typename Base, typename Impl,
+            std::enable_if_t<std::is_base_of<Base, Impl>::value, bool> = true>
+  static fruit::Component<Deps> OneBaseOneImpl() {
+    return fruit::createComponent().addMultibinding<Base, Impl>();
+  }
+  /* SFINAE logic for an individual interface binding. The class does not
+   * implement the interface, so do not add a multibinding. */
+  template <typename Base, typename Impl,
+            std::enable_if_t<!std::is_base_of<Base, Impl>::value, bool> = true>
+  static fruit::Component<Deps> OneBaseOneImpl() {
+    return fruit::createComponent();
+  }
+
+  template <typename Base>
+  struct OneBase {
+    template <typename... ImplTypes>
+    static fruit::Component<Deps> Impls() {
+      return fruit::createComponent().installComponentFunctions(
+          fruit::componentFunction(OneBaseOneImpl<Base, ImplTypes>)...);
+    }
+  };
+
+  template <typename... BaseTypes>
+  struct Bases {
+    template <typename... ImplTypes>
+    static fruit::Component<Deps> Impls() {
+      return fruit::createComponent().installComponentFunctions(
+          fruit::componentFunction(
+              OneBase<BaseTypes>::template Impls<ImplTypes...>)...);
+    }
+  };
+};
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/kernel_args.cpp b/host/libs/config/kernel_args.cpp
index 421950a..a90d386 100644
--- a/host/libs/config/kernel_args.cpp
+++ b/host/libs/config/kernel_args.cpp
@@ -80,7 +80,7 @@
         vm_manager_cmdline.push_back("ramoops.dump_oops=1");
       } else {
         // crosvm requires these additional parameters on x86_64 in bootloader mode
-        AppendVector(&vm_manager_cmdline, {"pci=noacpi", "reboot=k"});
+        AppendVector(&vm_manager_cmdline, {"acpi=noirq", "reboot=k"});
       }
     }
   }
diff --git a/host/libs/config/known_paths.cpp b/host/libs/config/known_paths.cpp
index 1581a06..bd95799 100644
--- a/host/libs/config/known_paths.cpp
+++ b/host/libs/config/known_paths.cpp
@@ -64,6 +64,13 @@
   return HostBinaryPath("tombstone_receiver");
 }
 
+std::string VehicleHalGrpcServerBinary() {
+  return HostBinaryPath(
+      "android.hardware.automotive.vehicle@2.0-virtualization-grpc-server");
+}
+
+std::string VncServerBinary() { return HostBinaryPath("vnc_server"); }
+
 std::string WebRtcBinary() {
   return HostBinaryPath("webRTC");
 }
@@ -72,8 +79,4 @@
   return HostBinaryPath("webrtc_operator");
 }
 
-std::string VncServerBinary() {
-  return HostBinaryPath("vnc_server");
-}
-
 } // namespace cuttlefish
diff --git a/host/libs/config/known_paths.h b/host/libs/config/known_paths.h
index 71b6253..635f1a8 100644
--- a/host/libs/config/known_paths.h
+++ b/host/libs/config/known_paths.h
@@ -30,8 +30,9 @@
 std::string RootCanalBinary();
 std::string SocketVsockProxyBinary();
 std::string TombstoneReceiverBinary();
+std::string VehicleHalGrpcServerBinary();
+std::string VncServerBinary();
 std::string WebRtcBinary();
 std::string WebRtcSigServerBinary();
-std::string VncServerBinary();
 
 } // namespace cuttlefish
diff --git a/host/libs/confui/host_mode_ctrl.h b/host/libs/confui/host_mode_ctrl.h
index afdc6e4..0950971 100644
--- a/host/libs/confui/host_mode_ctrl.h
+++ b/host/libs/confui/host_mode_ctrl.h
@@ -25,7 +25,6 @@
 #include "common/libs/confui/confui.h"
 #include "host/libs/confui/host_utils.h"
 
-using cuttlefish::confui::DebugLog;
 namespace cuttlefish {
 /**
  * mechanism to orchestrate concurrent executions of threads
@@ -59,11 +58,11 @@
    * amd64 desktop, with Linux 5.10
    */
   void WaitAndroidMode() {
-    DebugLog(cuttlefish::confui::thread::GetName(),
-             " checking atomic Android mode");
+    ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
+                     << "checking atomic Android mode";
     if (atomic_mode_ == ModeType::kAndroidMode) {
-      DebugLog(cuttlefish::confui::thread::GetName(),
-               " returns as it is already Android mode");
+      ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
+                       << "returns as it is already Android mode";
       return;
     }
     auto check = [this]() -> bool {
@@ -71,25 +70,25 @@
     };
     std::unique_lock<std::mutex> lock(mode_mtx_);
     and_mode_cv_.wait(lock, check);
-    DebugLog(cuttlefish::confui::thread::GetName(),
-             " awakes from cond var waiting for Android mode");
+    ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
+                     << "awakes from cond var waiting for Android mode";
   }
 
   void SetMode(const ModeType mode) {
-    DebugLog(cuttlefish::confui::thread::GetName(),
-             " tries to acquire the lock in SetMode");
+    ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
+                     << " tries to acquire the lock in SetMode";
     std::lock_guard<std::mutex> lock(mode_mtx_);
-    DebugLog(cuttlefish::confui::thread::GetName(),
-             " acquired the lock in SetMode");
+    ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
+                     << " acquired the lock in SetMode";
     atomic_mode_ = mode;
     if (atomic_mode_ == ModeType::kAndroidMode) {
-      DebugLog(cuttlefish::confui::thread::GetName(),
-               " signals kAndroidMode in SetMode");
+      ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
+                       << " signals kAndroidMode in SetMode";
       and_mode_cv_.notify_all();
       return;
     }
-    DebugLog(cuttlefish::confui::thread::GetName(),
-             " signals kConfUI_Mode in SetMode");
+    ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
+                     << "signals kConfUI_Mode in SetMode";
     confui_mode_cv_.notify_all();
   }
 
diff --git a/host/libs/confui/host_renderer.cc b/host/libs/confui/host_renderer.cc
index 65b30bf..f3e0c52 100644
--- a/host/libs/confui/host_renderer.cc
+++ b/host/libs/confui/host_renderer.cc
@@ -105,7 +105,7 @@
   layout_ = teeui::instantiateLayout(teeui::ConfUILayout(), ctx_);
   SetLangId(lang_id);
   if (auto error = UpdateTranslations()) {
-    ErrorLog("Update Translation Error");
+    ConfUiLog(ERROR) << "Update Translation Error";
     return false;
   }
   UpdateColorScheme(&ctx_);
@@ -120,7 +120,7 @@
   const auto width = ScreenConnectorInfo::ScreenWidth(display_num_);
   auto pos = width * y + x;
   if (pos >= (height * width)) {
-    ErrorLog("Rendering Out of Bound");
+    ConfUiLog(ERROR) << "Rendering Out of Bound";
     return teeui::Error::OutOfBoundsDrawing;
   }
   const double alfa = ((color & 0xff000000) >> 24) / 255.0;
@@ -202,7 +202,7 @@
   // render all components
   const auto error = drawElements(layout_, draw_pixel);
   if (error) {
-    ErrorLog("Painting failed: ", error.code());
+    ConfUiLog(ERROR) << "Painting failed: " << error.code();
     return std::nullopt;
   }
 
@@ -232,9 +232,10 @@
   }
 
   SetConfUiMessage(confirmation_msg);
-  DebugLog("Repaint Confirmation Msg with : ", prompt_);
+  ConfUiLog(DEBUG) << "Repaint Confirmation Msg with :" << prompt_;
   if (auto error = std::get<LabelConfMsg>(layout_).draw(draw_pixel)) {
-    ErrorLog("Repainting Confirmation Message Label failed: ", error.code());
+    ConfUiLog(ERROR) << "Repainting Confirmation Message Label failed:"
+                     << error.code();
     return error;
   }
   return teeui::Error::OK;
diff --git a/host/libs/confui/host_renderer.h b/host/libs/confui/host_renderer.h
index f44ab82..c614109 100644
--- a/host/libs/confui/host_renderer.h
+++ b/host/libs/confui/host_renderer.h
@@ -146,7 +146,8 @@
     auto& label = std::get<Label>(layout_);
     str = localization::lookup(TranslationId(label.textId()));
     if (str == nullptr) {
-      ErrorLog("Given translation_id ", label.textId(), " not found");
+      ConfUiLog(ERROR) << "Given translation_id" << label.textId()
+                       << "not found";
       return Error::Localization;
     }
     label.setText({str, str + strlen(str)});
diff --git a/host/libs/confui/host_server.cc b/host/libs/confui/host_server.cc
index c0ca97a..c3677d8 100644
--- a/host/libs/confui/host_server.cc
+++ b/host/libs/confui/host_server.cc
@@ -22,6 +22,7 @@
 #include <tuple>
 
 #include "common/libs/confui/confui.h"
+#include "common/libs/fs/shared_buf.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/confui/host_utils.h"
 
@@ -33,8 +34,8 @@
   return config->ForDefaultInstance();
 }
 
-static std::string HalGuestSocketPath() {
-  return CuttlefishConfigDefaultInstance().confui_hal_guest_socket_path();
+static int HalHostVsockPort() {
+  return CuttlefishConfigDefaultInstance().confui_host_vsock_port();
 }
 
 HostServer& HostServer::Get(
@@ -51,18 +52,24 @@
       host_mode_ctrl_(host_mode_ctrl),
       screen_connector_{screen_connector},
       renderer_(display_num_),
-      hal_socket_path_(HalGuestSocketPath()),
-      input_multiplexer_{/* max n_elems */ 20, /* n_Qs */ 2} {
-  hal_cmd_q_id_ = input_multiplexer_.GetNewQueueId();         // return 0
-  user_input_evt_q_id_ = input_multiplexer_.GetNewQueueId();  // return 1
+      hal_vsock_port_(HalHostVsockPort()) {
+  const size_t max_elements = 20;
+  auto ignore_new = [](ThreadSafeQueue<ConfUiMessage>::QueueImpl*) {
+    // no op, so the queue is still full, and the new item will be discarded
+    return;
+  };
+  hal_cmd_q_id_ = input_multiplexer_.RegisterQueue(
+      HostServer::Multiplexer::CreateQueue(max_elements, ignore_new));
+  user_input_evt_q_id_ = input_multiplexer_.RegisterQueue(
+      HostServer::Multiplexer::CreateQueue(max_elements, ignore_new));
 }
 
 void HostServer::Start() {
-  guest_hal_socket_ = cuttlefish::SharedFD::SocketLocalServer(
-      hal_socket_path_, false, SOCK_STREAM, 0666);
+  guest_hal_socket_ =
+      cuttlefish::SharedFD::VsockServer(hal_vsock_port_, SOCK_STREAM);
   if (!guest_hal_socket_->IsOpen()) {
-    FatalLog("Confirmation UI host service mandates a server socket",
-             "to which the guest HAL to connect.");
+    ConfUiLog(FATAL) << "Confirmation UI host service mandates a server socket"
+                     << "to which the guest HAL to connect.";
     return;
   }
   auto hal_cmd_fetching = [this]() { this->HalCmdFetcherLoop(); };
@@ -70,20 +77,21 @@
   hal_input_fetcher_thread_ =
       thread::RunThread("HalInputLoop", hal_cmd_fetching);
   main_loop_thread_ = thread::RunThread("MainLoop", main);
-  DebugLog("configured internal socket based input.");
+  ConfUiLog(DEBUG) << "configured internal vsock based input.";
   return;
 }
 
 void HostServer::HalCmdFetcherLoop() {
   hal_cli_socket_ = EstablishHalConnection();
   if (!hal_cli_socket_->IsOpen()) {
-    FatalLog("Confirmation UI host service mandates connection with HAL.");
+    ConfUiLog(FATAL)
+        << "Confirmation UI host service mandates connection with HAL.";
     return;
   }
   while (true) {
     auto opted_msg = RecvConfUiMsg(hal_cli_socket_);
     if (!opted_msg) {
-      ErrorLog("Error in RecvConfUiMsg from HAL");
+      ConfUiLog(ERROR) << "Error in RecvConfUiMsg from HAL";
       continue;
     }
     auto input = std::move(opted_msg.value());
@@ -93,7 +101,7 @@
 
 bool HostServer::SendUserSelection(UserResponse::type selection) {
   if (!curr_session_) {
-    FatalLog("Current session must not be null");
+    ConfUiLog(FATAL) << "Current session must not be null";
     return false;
   }
   if (curr_session_->GetState() != MainLoopState::kInSession) {
@@ -104,8 +112,8 @@
   std::lock_guard<std::mutex> lock(input_socket_mtx_);
   if (selection != UserResponse::kConfirm &&
       selection != UserResponse::kCancel) {
-    FatalLog(selection, " must be either ", UserResponse::kConfirm, " or ",
-             UserResponse::kCancel);
+    ConfUiLog(FATAL) << selection << " must be either" << UserResponse::kConfirm
+                     << "or" << UserResponse::kCancel;
     return false;  // not reaching here
   }
 
@@ -140,17 +148,18 @@
 }
 
 SharedFD HostServer::EstablishHalConnection() {
-  DebugLog("Waiting hal accepting");
+  ConfUiLog(DEBUG) << "Waiting hal accepting";
   auto new_cli = SharedFD::Accept(*guest_hal_socket_);
-  DebugLog("hal client accepted");
+  ConfUiLog(DEBUG) << "hal client accepted";
   return new_cli;
 }
 
 std::unique_ptr<Session> HostServer::ComputeCurrentSession(
     const std::string& session_id) {
   if (curr_session_ && (GetCurrentSessionId() != session_id)) {
-    FatalLog(curr_session_->GetId(), " is active and in the ",
-             GetCurrentState(), " but HAL sends command to ", session_id);
+    ConfUiLog(FATAL) << curr_session_->GetId() << " is active and in the"
+                     << GetCurrentState() << "but HAL sends command to"
+                     << session_id;
   }
   if (curr_session_) {
     return std::move(curr_session_);
@@ -184,17 +193,18 @@
     const bool is_user_input = (cmd == ConfUiCmd::kUserInputEvent);
     std::string src = is_user_input ? "input" : "hal";
 
-    DebugLog("In Session ", GetCurrentSessionId(), "m in state ",
-             GetCurrentState(), " received input from ", src,
-             " cmd = ", cmd_str, " and additional_info = " + additional_info,
-             " going to session ", session_id);
+    ConfUiLog(DEBUG) << "In Session" << GetCurrentSessionId() << ", "
+                     << "in state" << GetCurrentState() << ", "
+                     << "received input from " << src << " cmd =" << cmd_str
+                     << " and additional_info =" << additional_info
+                     << " going to session " << session_id;
 
     FsmInput fsm_input = ToFsmInput(input);
 
     if (is_user_input && !curr_session_) {
       // discard the input, there's no session to take it yet
       // actually, no confirmation UI screen is available
-      DebugLog("Took user input but no active session is available.");
+      ConfUiLog(DEBUG) << "Took user input but no active session is available.";
       continue;
     }
 
@@ -207,11 +217,12 @@
      *
      */
     curr_session_ = ComputeCurrentSession(session_id);
-    DebugLog("Host service picked up ",
-             (curr_session_ ? curr_session_->GetId() : "null session"));
-    DebugLog(
-        "The state of current session is ",
-        curr_session_ ? ToString(curr_session_->GetState()) : "null session");
+    ConfUiLog(DEBUG) << "Host service picked up "
+                     << (curr_session_ ? curr_session_->GetId()
+                                       : "null session");
+    ConfUiLog(DEBUG) << "The state of current session is "
+                     << (curr_session_ ? ToString(curr_session_->GetState())
+                                       : "null session");
 
     if (is_user_input) {
       curr_session_->Transition(is_user_input, hal_cli_socket_, fsm_input,
diff --git a/host/libs/confui/host_server.h b/host/libs/confui/host_server.h
index 05164c9..b718db4 100644
--- a/host/libs/confui/host_server.h
+++ b/host/libs/confui/host_server.h
@@ -28,6 +28,7 @@
 #include <android-base/logging.h>
 #include <teeui/utils.h>
 
+#include "common/libs/concurrency/multiplexer.h"
 #include "common/libs/concurrency/semaphore.h"
 #include "common/libs/confui/confui.h"
 #include "common/libs/fs/shared_fd.h"
@@ -36,7 +37,6 @@
 #include "host/libs/confui/host_mode_ctrl.h"
 #include "host/libs/confui/host_renderer.h"
 #include "host/libs/confui/host_virtual_input.h"
-#include "host/libs/confui/multiplexer.h"
 #include "host/libs/confui/server_common.h"
 #include "host/libs/confui/session.h"
 #include "host/libs/screen_connector/screen_connector.h"
@@ -154,7 +154,7 @@
   ConfUiRenderer renderer_;
 
   std::string input_socket_path_;
-  std::string hal_socket_path_;
+  int hal_vsock_port_;
 
   // session id to Session object map, for those that are suspended
   std::unordered_map<std::string, std::unique_ptr<Session>> session_map_;
@@ -166,6 +166,8 @@
   SharedFD hal_cli_socket_;
   std::mutex input_socket_mtx_;
 
+  using Multiplexer =
+      Multiplexer<ConfUiMessage, ThreadSafeQueue<ConfUiMessage>>;
   /*
    * Multiplexer has N queues. When pop(), it is going to sleep until
    * there's at least one item in at least one queue. The lower the Q
@@ -174,7 +176,7 @@
    * For HostServer, we have a queue for the user input events, and
    * another for hal cmd/msg queues
    */
-  Multiplexer<ConfUiMessage> input_multiplexer_;
+  Multiplexer input_multiplexer_;
   int hal_cmd_q_id_;         // Q id in input_multiplexer_
   int user_input_evt_q_id_;  // Q id in input_multiplexer_
 
diff --git a/host/libs/confui/host_utils.cc b/host/libs/confui/host_utils.cc
index 8f4335b..6ff67b0 100644
--- a/host/libs/confui/host_utils.cc
+++ b/host/libs/confui/host_utils.cc
@@ -34,7 +34,7 @@
   if (name2id_.find(name) != name2id_.end()) {
     // has the name already
     if (name2id_[name] != tid) {  // used for another thread
-      FatalLog("Thread name is duplicated.");
+      ConfUiLog(FATAL) << "Thread name is duplicated.";
     }
     // name and id are already set correctly
     return;
diff --git a/host/libs/confui/host_utils.h b/host/libs/confui/host_utils.h
index 2e9c7eb..bd4004b 100644
--- a/host/libs/confui/host_utils.h
+++ b/host/libs/confui/host_utils.h
@@ -69,11 +69,11 @@
   std::thread RunThread(const std::string& name, F&& f, Args&&... args) {
     auto th = std::thread(std::forward<F>(f), std::forward<Args>(args)...);
     if (name2id_.find(name) != name2id_.end()) {
-      FatalLog("Thread name duplicated");
+      ConfUiLog(FATAL) << "Thread name is duplicated";
     }
     name2id_[name] = th.get_id();
     id2name_[th.get_id()] = name;
-    DebugLog(name, " thread started.");
+    ConfUiLog(DEBUG) << name << "thread started.";
     return th;
   }
   std::string Get(const std::thread::id id = std::this_thread::get_id());
diff --git a/host/libs/confui/multiplexer.h b/host/libs/confui/multiplexer.h
deleted file mode 100644
index 2ac1928..0000000
--- a/host/libs/confui/multiplexer.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <condition_variable>
-#include <deque>
-#include <memory>
-#include <mutex>
-#include <thread>
-
-#include "common/libs/concurrency/semaphore.h"
-#include "common/libs/concurrency/thread_safe_queue.h"
-
-namespace cuttlefish {
-namespace confui {
-template <typename T>
-class Multiplexer {
- public:
-  Multiplexer(int n_qs, int max_elements) : sem_items_{0}, next_{0} {
-    auto drop_new = [](typename ThreadSafeQueue<T>::QueueImpl* internal_q) {
-      internal_q->pop_front();
-    };
-    for (int i = 0; i < n_qs; i++) {
-      auto queue = std::make_unique<ThreadSafeQueue<T>>(max_elements, drop_new);
-      queues_.push_back(std::move(queue));
-    }
-  }
-
-  int GetNewQueueId() {
-    CHECK(next_ < queues_.size())
-        << "can't get more queues than " << queues_.size();
-    return next_++;
-  }
-
-  void Push(const int idx, T&& t) {
-    CheckIdx(idx);
-    queues_[idx]->Push(t);
-    sem_items_.SemPost();
-  }
-
-  T Pop() {
-    // the idx must have an item!
-    // no waiting in fn()!
-    sem_items_.SemWait();
-    for (auto& q : queues_) {
-      if (q->IsEmpty()) {
-        continue;
-      }
-      return q->Pop();
-    }
-    CHECK(false) << "Multiplexer.Pop() should be able to return an item";
-    // must not reach here
-    return T{};
-  }
-
- private:
-  void CheckIdx(const int idx) {
-    CHECK(idx >= 0 && idx < queues_.size()) << "queues_ array out of bound";
-  }
-  // total items across the queues
-  Semaphore sem_items_;
-  std::vector<std::unique_ptr<ThreadSafeQueue<T>>> queues_;
-  int next_;
-};
-}  // end of namespace confui
-}  // end of namespace cuttlefish
diff --git a/host/libs/confui/server_common.cc b/host/libs/confui/server_common.cc
index b6cc208..cf46fe3 100644
--- a/host/libs/confui/server_common.cc
+++ b/host/libs/confui/server_common.cc
@@ -49,7 +49,8 @@
     case ConfUiCmd::kCliAck:
     case ConfUiCmd::kCliRespond:
     default:
-      FatalLog("The ", ToString(hal_cmd), " is not handled by Session");
+      ConfUiLog(FATAL) << "The" << ToString(hal_cmd)
+                       << "is not handled by Session";
   }
   return FsmInput::kHalUnknown;
 }
diff --git a/host/libs/confui/session.cc b/host/libs/confui/session.cc
index 92dbd7f..07015cb 100644
--- a/host/libs/confui/session.cc
+++ b/host/libs/confui/session.cc
@@ -48,9 +48,15 @@
   prompt_ = msg;
   locale_ = locale;
 
-  DebugLog("actually trying to render the frame ", thread::GetName());
-  auto raw_frame = reinterpret_cast<std::uint8_t*>(teeui_frame.data());
-  return screen_connector_.RenderConfirmationUi(display_num_, raw_frame);
+  ConfUiLog(DEBUG) << "actually trying to render the frame"
+                   << thread::GetName();
+  auto frame_width = ScreenConnectorInfo::ScreenWidth(display_num_);
+  auto frame_height = ScreenConnectorInfo::ScreenHeight(display_num_);
+  auto frame_stride_bytes =
+      ScreenConnectorInfo::ScreenStrideBytes(display_num_);
+  auto frame_bytes = reinterpret_cast<std::uint8_t*>(teeui_frame.data());
+  return screen_connector_.RenderConfirmationUi(
+      display_num_, frame_width, frame_height, frame_stride_bytes, frame_bytes);
 }
 
 bool Session::IsSuspended() const {
@@ -69,14 +75,15 @@
     } break;
     case MainLoopState::kWaitStop: {
       if (is_user_input) {
-        DebugLog("User input ignored ", ToString(fsm_input), " : ",
-                 additional_info, " at state ", ToString(state_));
+        ConfUiLog(DEBUG) << "User input ignored " << ToString(fsm_input)
+                         << " : " << additional_info << " at the state "
+                         << ToString(state_);
       }
       HandleWaitStop(is_user_input, hal_cli, fsm_input);
     } break;
     default:
       // host service explicitly calls restore and suspend
-      FatalLog("Must not be in the state of ", ToString(state_));
+      ConfUiLog(FATAL) << "Must not be in the state of " << ToString(state_);
       break;
   }
   return state_;
@@ -85,11 +92,12 @@
 bool Session::Suspend(SharedFD hal_cli) {
   if (state_ == MainLoopState::kInit) {
     // HAL sent wrong command
-    FatalLog("HAL sent wrong command, suspend, when the session is in kIinit");
+    ConfUiLog(FATAL)
+        << "HAL sent wrong command, suspend, when the session is in kIinit";
     return false;
   }
   if (state_ == MainLoopState::kSuspended) {
-    DebugLog("Already kSuspended state");
+    ConfUiLog(DEBUG) << "Already kSuspended state";
     return false;
   }
   saved_state_ = state_;
@@ -97,7 +105,7 @@
   host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kAndroidMode);
   if (!packet::SendAck(hal_cli, session_id_, /*is success*/ true,
                        "suspended")) {
-    FatalLog("I/O error");
+    ConfUiLog(FATAL) << "I/O error";
     return false;
   }
   return true;
@@ -106,30 +114,30 @@
 bool Session::Restore(SharedFD hal_cli) {
   if (state_ == MainLoopState::kInit) {
     // HAL sent wrong command
-    FatalLog("HAL sent wrong command, restore, when the session is in kIinit");
+    ConfUiLog(FATAL)
+        << "HAL sent wrong command, restore, when the session is in kIinit";
     return false;
   }
 
   if (state_ != MainLoopState::kSuspended) {
-    DebugLog("Already Restored to state " + ToString(state_));
+    ConfUiLog(DEBUG) << "Already Restored to state " + ToString(state_);
     return false;
   }
   host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kConfUI_Mode);
   if (!RenderDialog(prompt_, locale_)) {
     // the confirmation UI is driven by a user app, not running from the start
     // automatically so that means webRTC/vnc should have been set up
-    ErrorLog(
-        "Dialog is not rendered. However, it should. No webRTC can't initiate "
-        "any confirmation UI.");
+    ConfUiLog(ERROR) << "Dialog is not rendered. However, it should."
+                     << "No webRTC can't initiate any confirmation UI.";
     if (!packet::SendAck(hal_cli, session_id_, false,
                          "render failed in restore")) {
-      FatalLog("Rendering failed in restore, and ack failed in I/O");
+      ConfUiLog(FATAL) << "Rendering failed in restore, and ack failed in I/O";
     }
     state_ = MainLoopState::kInit;
     return false;
   }
   if (!packet::SendAck(hal_cli, session_id_, true, "restored")) {
-    FatalLog("Ack to restore failed in I/O");
+    ConfUiLog(FATAL) << "Ack to restore failed in I/O";
   }
   state_ = saved_state_;
   saved_state_ = MainLoopState::kInit;
@@ -140,7 +148,7 @@
   state_ = MainLoopState::kAwaitCleanup;
   saved_state_ = MainLoopState::kInvalid;
   if (!packet::SendAck(hal_cli, session_id_, true, response_msg)) {
-    FatalLog("I/O error in ack to Abort");
+    ConfUiLog(FATAL) << "I/O error in ack to Abort";
     return false;
   }
   return true;
@@ -148,7 +156,7 @@
 
 void Session::CleanUp() {
   if (state_ != MainLoopState::kAwaitCleanup) {
-    FatalLog("Clean up a session only when in kAwaitCleanup");
+    ConfUiLog(FATAL) << "Clean up a session only when in kAwaitCleanup";
   }
   // common action done when the state is back to init state
   host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kAndroidMode);
@@ -159,7 +167,7 @@
   // session id
   state_ = MainLoopState::kAwaitCleanup;
   if (!packet::SendAck(hal_cli, session_id_, false, msg)) {
-    FatalLog("I/O error in sending ack to report rendering failure");
+    ConfUiLog(FATAL) << "I/O error in sending ack to report rendering failure";
   }
   return;
 }
@@ -176,9 +184,9 @@
     return;
   }
 
-  DebugLog(ToString(fsm_input), " is handled in HandleInit");
+  ConfUiLog(DEBUG) << ToString(fsm_input) << "is handled in HandleInit";
   if (fsm_input != FsmInput::kHalStart) {
-    ErrorLog("invalid cmd for Init State: ", ToString(fsm_input));
+    ConfUiLog(ERROR) << "invalid cmd for Init State:" << ToString(fsm_input);
     // reset the session -- destroy it & recreate it with the same
     // session id
     ReportErrorToHal(hal_cli, "wrong hal command");
@@ -186,20 +194,20 @@
   }
 
   // Start Session
-  DebugLog("Sending ack to hal_cli: ", Enum2Base(ConfUiCmd::kCliAck));
+  ConfUiLog(DEBUG) << "Sending ack to hal_cli: "
+                   << Enum2Base(ConfUiCmd::kCliAck);
   host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kConfUI_Mode);
   auto confirmation_msg = additional_info;
   if (!RenderDialog(confirmation_msg, locale_)) {
     // the confirmation UI is driven by a user app, not running from the start
     // automatically so that means webRTC/vnc should have been set up
-    ErrorLog(
-        "Dialog is not rendered. However, it should. No webRTC can't initiate "
-        "any confirmation UI.");
+    ConfUiLog(ERROR) << "Dialog is not rendered. However, it should."
+                     << "No webRTC can't initiate any confirmation UI.";
     ReportErrorToHal(hal_cli, "rendering failed");
     return;
   }
   if (!packet::SendAck(hal_cli, session_id_, true, "started")) {
-    FatalLog("Ack to kStart failed in I/O");
+    ConfUiLog(FATAL) << "Ack to kStart failed in I/O";
   }
   state_ = MainLoopState::kInSession;
   return;
@@ -208,8 +216,8 @@
 void Session::HandleInSession(const bool is_user_input, SharedFD hal_cli,
                               const FsmInput fsm_input) {
   if (!is_user_input) {
-    FatalLog("cmd", ToString(fsm_input),
-             "should not be handled in HandleInSession");
+    ConfUiLog(FATAL) << "cmd " << ToString(fsm_input)
+                     << " should not be handled in HandleInSession";
     ReportErrorToHal(hal_cli, "wrong hal command");
     return;
   }
@@ -225,19 +233,19 @@
     if (!packet::SendAck(hal_cli, session_id_, true,
                          "invalid user input error")) {
       // note that input is what we control in memory
-      PassOrDie(false, "Input must be either confirm or cancel for now.");
+      ConfUiCheck(false) << "Input must be either confirm or cancel for now.";
     }
     return;
   }
 
-  DebugLog("In HandlieInSession, session ", session_id_,
-           " is sending the user input ", ToString(fsm_input));
+  ConfUiLog(DEBUG) << "In HandlieInSession, session " << session_id_
+                   << " is sending the user input " << ToString(fsm_input);
   auto selection = UserResponse::kConfirm;
   if (fsm_input == FsmInput::kUserCancel) {
     selection = UserResponse::kCancel;
   }
   if (!packet::SendResponse(hal_cli, session_id_, selection)) {
-    FatalLog("I/O error in sending user response to HAL");
+    ConfUiLog(FATAL) << "I/O error in sending user response to HAL";
   }
   state_ = MainLoopState::kWaitStop;
   return;
@@ -253,11 +261,12 @@
     return;
   }
   if (fsm_input == FsmInput::kHalStop) {
-    DebugLog("Handling Abort in kWaitStop.");
+    ConfUiLog(DEBUG) << "Handling Abort in kWaitStop.";
     Kill(hal_cli, "stopped");
     return;
   }
-  FatalLog("In WaitStop, received wrong HAL command ", ToString(fsm_input));
+  ConfUiLog(FATAL) << "In WaitStop, received wrong HAL command "
+                   << ToString(fsm_input);
   state_ = MainLoopState::kAwaitCleanup;
   return;
 }
diff --git a/host/libs/graphics_detector/Android.bp b/host/libs/graphics_detector/Android.bp
index bd6a55e..679825a 100644
--- a/host/libs/graphics_detector/Android.bp
+++ b/host/libs/graphics_detector/Android.bp
@@ -36,6 +36,7 @@
     ],
     header_libs: [
         "egl_headers",
+        "gl_headers",
         "vulkan_headers",
     ],
     shared_libs: [
@@ -44,3 +45,19 @@
     ],
     defaults: ["cuttlefish_host"],
 }
+
+cc_binary {
+    name: "detect_graphics",
+    srcs: [
+        "detect_graphics.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    static_libs: [
+        "libcuttlefish_graphics_detector",
+        "libgflags",
+    ],
+    defaults: ["cuttlefish_host"],
+}
diff --git a/host/libs/graphics_detector/detect_graphics.cpp b/host/libs/graphics_detector/detect_graphics.cpp
new file mode 100644
index 0000000..6032520
--- /dev/null
+++ b/host/libs/graphics_detector/detect_graphics.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#include <string>
+
+#include <gflags/gflags.h>
+
+#include "android-base/logging.h"
+#include "host/libs/graphics_detector/graphics_detector.h"
+
+int main(int argc, char* argv[]) {
+  ::android::base::InitLogging(argv, android::base::StdioLogger);
+  ::android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+  ::gflags::ParseCommandLineFlags(&argc, &argv, true);
+  LOG(INFO) << cuttlefish::GetGraphicsAvailabilityWithSubprocessCheck();
+}
\ No newline at end of file
diff --git a/host/libs/graphics_detector/graphics_detector.cpp b/host/libs/graphics_detector/graphics_detector.cpp
index 367e637..57c40f9 100644
--- a/host/libs/graphics_detector/graphics_detector.cpp
+++ b/host/libs/graphics_detector/graphics_detector.cpp
@@ -21,6 +21,7 @@
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
+#include <GLES2/gl2.h>
 #include <android-base/logging.h>
 #include <android-base/strings.h>
 #include <dlfcn.h>
@@ -30,7 +31,7 @@
 namespace cuttlefish {
 namespace {
 
-constexpr const char kEglLib[] = "libEGL.so.1";
+constexpr const char kEglLib[] = "libEGL.so";
 constexpr const char kGlLib[] = "libOpenGL.so.0";
 constexpr const char kGles1Lib[] = "libGLESv1_CM.so.1";
 constexpr const char kGles2Lib[] = "libGLESv2.so.2";
@@ -128,31 +129,6 @@
   }
   LOG(VERBOSE) << "Loaded eglGetDisplay.";
 
-  EGLDisplay default_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-  if (default_display == EGL_NO_DISPLAY) {
-    LOG(VERBOSE) << "Failed to get default display. " << eglGetError();
-    return;
-  }
-  LOG(VERBOSE) << "Found default display.";
-  availability->has_egl_default_display = true;
-
-  PFNEGLINITIALIZEPROC eglInitialize =
-    reinterpret_cast<PFNEGLINITIALIZEPROC>(EglLoadFunction("eglInitialize"));
-  if (eglInitialize == nullptr) {
-    LOG(VERBOSE) << "Failed to find function eglQueryString";
-    return;
-  }
-
-  EGLint client_version_major = 0;
-  EGLint client_version_minor = 0;
-  if (eglInitialize(default_display,
-                    &client_version_major,
-                    &client_version_minor) != EGL_TRUE) {
-    LOG(VERBOSE) << "Failed to initialize default display.";
-    return;
-  }
-  LOG(VERBOSE) << "Initialized default display.";
-
   PFNEGLQUERYSTRINGPROC eglQueryString =
     reinterpret_cast<PFNEGLQUERYSTRINGPROC>(EglLoadFunction("eglQueryString"));
   if (eglQueryString == nullptr) {
@@ -161,49 +137,46 @@
   }
   LOG(VERBOSE) << "Loaded eglQueryString.";
 
-  std::string client_extensions;
-  if (client_version_major >= 1 && client_version_minor >= 5) {
-    client_extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
-  }
-  availability->egl_client_extensions = client_extensions;
-
-  EGLDisplay display = EGL_NO_DISPLAY;
-
-  if (client_extensions.find("EGL_EXT_platform_base") != std::string::npos) {
-    LOG(VERBOSE) << "Client extension EGL_EXT_platform_base is supported.";
+  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+  if (display != EGL_NO_DISPLAY) {
+    LOG(VERBOSE) << "Found default display.";
+  } else {
+    LOG(VERBOSE) << "Failed to get default display. " << eglGetError()
+                 << ". Attempting to get surfaceless display via "
+                 << "eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA)";
 
     PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT =
       reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
         EglLoadFunction("eglGetPlatformDisplayEXT"));
     if (eglGetPlatformDisplayEXT == nullptr) {
       LOG(VERBOSE) << "Failed to find function eglGetPlatformDisplayEXT";
-      return;
+    } else {
+      display = eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA,
+                                         EGL_DEFAULT_DISPLAY, NULL);
     }
+  }
 
-    display =
-      eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA,
-                               EGL_DEFAULT_DISPLAY,
-                               NULL);
-  } else {
-    LOG(VERBOSE) << "Failed to find client extension EGL_EXT_platform_base.";
-  }
-  if (display == EGL_NO_DISPLAY) {
-    LOG(VERBOSE) << "Failed to get EGL_PLATFORM_SURFACELESS_MESA display..."
-                 << "failing back to EGL_DEFAULT_DISPLAY display.";
-    display = default_display;
-  }
   if (display == EGL_NO_DISPLAY) {
     LOG(VERBOSE) << "Failed to find display.";
     return;
   }
 
+  PFNEGLINITIALIZEPROC eglInitialize =
+      reinterpret_cast<PFNEGLINITIALIZEPROC>(EglLoadFunction("eglInitialize"));
+  if (eglInitialize == nullptr) {
+    LOG(VERBOSE) << "Failed to find function eglQueryString";
+    return;
+  }
+
+  EGLint client_version_major = 0;
+  EGLint client_version_minor = 0;
   if (eglInitialize(display,
                     &client_version_major,
                     &client_version_minor) != EGL_TRUE) {
-    LOG(VERBOSE) << "Failed to initialize surfaceless display.";
+    LOG(VERBOSE) << "Failed to initialize display.";
     return;
   }
-  LOG(VERBOSE) << "Initialized surfaceless display.";
+  LOG(VERBOSE) << "Initialized display.";
 
   const std::string version_string = eglQueryString(display, EGL_VERSION);
   if (version_string.empty()) {
@@ -337,7 +310,46 @@
     return;
   }
   LOG(VERBOSE) << "Make EGL context current.";
-  availability->has_egl_surfaceless_with_gles = true;
+  availability->can_init_gles2_on_egl_surfaceless = true;
+
+  PFNGLGETSTRINGPROC glGetString =
+      reinterpret_cast<PFNGLGETSTRINGPROC>(eglGetProcAddress("glGetString"));
+
+  const GLubyte* gles2_vendor = glGetString(GL_VENDOR);
+  if (gles2_vendor == nullptr) {
+    LOG(VERBOSE) << "Failed to query GLES2 vendor.";
+    return;
+  }
+  const std::string gles2_vendor_string((const char*)gles2_vendor);
+  LOG(VERBOSE) << "Found GLES2 vendor: " << gles2_vendor_string;
+  availability->gles2_vendor = gles2_vendor_string;
+
+  const GLubyte* gles2_version = glGetString(GL_VERSION);
+  if (gles2_version == nullptr) {
+    LOG(VERBOSE) << "Failed to query GLES2 vendor.";
+    return;
+  }
+  const std::string gles2_version_string((const char*)gles2_version);
+  LOG(VERBOSE) << "Found GLES2 version: " << gles2_version_string;
+  availability->gles2_version = gles2_version_string;
+
+  const GLubyte* gles2_renderer = glGetString(GL_RENDERER);
+  if (gles2_renderer == nullptr) {
+    LOG(VERBOSE) << "Failed to query GLES2 renderer.";
+    return;
+  }
+  const std::string gles2_renderer_string((const char*)gles2_renderer);
+  LOG(VERBOSE) << "Found GLES2 renderer: " << gles2_renderer_string;
+  availability->gles2_renderer = gles2_renderer_string;
+
+  const GLubyte* gles2_extensions = glGetString(GL_EXTENSIONS);
+  if (gles2_extensions == nullptr) {
+    LOG(VERBOSE) << "Failed to query GLES2 extensions.";
+    return;
+  }
+  const std::string gles2_extensions_string((const char*)gles2_extensions);
+  LOG(VERBOSE) << "Found GLES2 extensions: " << gles2_extensions_string;
+  availability->gles2_extensions = gles2_extensions_string;
 }
 
 void PopulateVulkanAvailability(GraphicsAvailability* availability) {
@@ -497,6 +509,8 @@
     VkPhysicalDeviceProperties device_properties = {};
     vkGetPhysicalDeviceProperties(device, &device_properties);
 
+    LOG(VERBOSE) << "Found physical device: " << device_properties.deviceName;
+
     uint32_t device_extensions_count = 0;
     vkEnumerateDeviceExtensionProperties(device,
                                          nullptr,
@@ -519,6 +533,9 @@
     std::string device_extensions_string =
       android::base::Join(device_extensions_strings, ' ');
 
+    LOG(VERBOSE) << "Found physical device extensions: "
+                 << device_extensions_string;
+
     if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
       availability->has_discrete_gpu = true;
       availability->discrete_gpu_device_name = device_properties.deviceName;
@@ -528,6 +545,18 @@
   }
 }
 
+std::string ToLower(const std::string& v) {
+  std::string result = v;
+  std::transform(result.begin(), result.end(), result.begin(),
+                 [](unsigned char c) { return std::tolower(c); });
+  return result;
+}
+
+bool IsLikelySoftwareRenderer(const std::string& renderer) {
+  const std::string lower_renderer = ToLower(renderer);
+  return lower_renderer.find("llvmpipe") != std::string::npos;
+}
+
 GraphicsAvailability GetGraphicsAvailability() {
   GraphicsAvailability availability;
 
@@ -544,7 +573,8 @@
 
 bool ShouldEnableAcceleratedRendering(
     const GraphicsAvailability& availability) {
-  return availability.has_egl && availability.has_egl_surfaceless_with_gles &&
+  return availability.can_init_gles2_on_egl_surfaceless &&
+         !IsLikelySoftwareRenderer(availability.gles2_renderer) &&
          availability.has_discrete_gpu;
 }
 
@@ -560,7 +590,7 @@
   }
   int status;
   if (waitpid(pid, &status, 0) != pid) {
-    PLOG(ERROR) << "Failed to wait for graphics check subprocess";
+    PLOG(VERBOSE) << "Failed to wait for graphics check subprocess";
     return GraphicsAvailability{};
   }
   if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
@@ -575,20 +605,32 @@
   std::ios_base::fmtflags flags_backup(stream.flags());
   stream << std::boolalpha;
   stream << "Graphics Availability:\n";
-  stream << "OpenGL available: " << availability.has_gl << "\n";
-  stream << "OpenGL ES1 available: " << availability.has_gles1 << "\n";
-  stream << "OpenGL ES2 available: " << availability.has_gles2 << "\n";
-  stream << "EGL available: " << availability.has_egl << "\n";
+
+  stream << "\n";
+  stream << "OpenGL lib available: " << availability.has_gl << "\n";
+  stream << "OpenGL ES1 lib available: " << availability.has_gles1 << "\n";
+  stream << "OpenGL ES2 lib available: " << availability.has_gles2 << "\n";
+  stream << "EGL lib available: " << availability.has_egl << "\n";
+  stream << "Vulkan lib available: " << availability.has_vulkan << "\n";
+
+  stream << "\n";
   stream << "EGL client extensions: " << availability.egl_client_extensions
          << "\n";
-  stream << "EGL default display available: "
-         << availability.has_egl_default_display << "\n";
+
+  stream << "\n";
   stream << "EGL display vendor: " << availability.egl_vendor << "\n";
   stream << "EGL display version: " << availability.egl_version << "\n";
   stream << "EGL display extensions: " << availability.egl_extensions << "\n";
-  stream << "EGL surfaceless display with GLES: "
-         << availability.has_egl_surfaceless_with_gles << "\n";
-  stream << "Vulkan available: " << availability.has_vulkan << "\n";
+
+  stream << "GLES2 can init on surfaceless display: "
+         << availability.can_init_gles2_on_egl_surfaceless << "\n";
+  stream << "\n";
+  stream << "GLES2 vendor: " << availability.gles2_vendor << "\n";
+  stream << "GLES2 version: " << availability.gles2_version << "\n";
+  stream << "GLES2 renderer: " << availability.gles2_renderer << "\n";
+  stream << "GLES2 extensions: " << availability.gles2_extensions << "\n";
+
+  stream << "\n";
   stream << "Vulkan discrete GPU detected: " << availability.has_discrete_gpu
          << "\n";
   if (availability.has_discrete_gpu) {
@@ -597,6 +639,11 @@
     stream << "Vulkan discrete GPU device extensions: "
            << availability.discrete_gpu_device_extensions << "\n";
   }
+
+  stream << "\n";
+  stream << "Accelerated rendering supported: "
+         << ShouldEnableAcceleratedRendering(availability);
+
   stream.flags(flags_backup);
   return stream;
 }
diff --git a/host/libs/graphics_detector/graphics_detector.h b/host/libs/graphics_detector/graphics_detector.h
index 83a7068..1bacc3d 100644
--- a/host/libs/graphics_detector/graphics_detector.h
+++ b/host/libs/graphics_detector/graphics_detector.h
@@ -25,13 +25,20 @@
   bool has_gles1 = false;
   bool has_gles2 = false;
   bool has_egl = false;
-  bool has_egl_default_display = false;
+  bool has_vulkan = false;
+
   std::string egl_client_extensions;
+
   std::string egl_version;
   std::string egl_vendor;
   std::string egl_extensions;
-  bool has_egl_surfaceless_with_gles = false;
-  bool has_vulkan = false;
+
+  bool can_init_gles2_on_egl_surfaceless = false;
+  std::string gles2_vendor;
+  std::string gles2_version;
+  std::string gles2_renderer;
+  std::string gles2_extensions;
+
   bool has_discrete_gpu = false;
   std::string discrete_gpu_device_name;
   std::string discrete_gpu_device_extensions;
diff --git a/host/libs/image_aggregator/Android.bp b/host/libs/image_aggregator/Android.bp
index 491a777..23d4874 100644
--- a/host/libs/image_aggregator/Android.bp
+++ b/host/libs/image_aggregator/Android.bp
@@ -23,7 +23,7 @@
         "cdisk_spec.proto",
     ],
     proto: {
-        type: "full",
+        type: "lite",
         export_proto_headers: true,
         include_dirs: [
             "external/protobuf/src",
@@ -37,11 +37,12 @@
     srcs: [
         "image_aggregator.cc",
     ],
+    export_include_dirs: ["."],
     shared_libs: [
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libbase",
-        "libprotobuf-cpp-full",
+        "libprotobuf-cpp-lite",
         "libz",
     ],
     static_libs: [
diff --git a/host/libs/image_aggregator/image_aggregator.cc b/host/libs/image_aggregator/image_aggregator.cc
index 8d4027a..0868aa1 100644
--- a/host/libs/image_aggregator/image_aggregator.cc
+++ b/host/libs/image_aggregator/image_aggregator.cc
@@ -31,6 +31,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/strings.h>
 #include <cdisk_spec.pb.h>
 #include <google/protobuf/text_format.h>
 #include <sparse/sparse.h>
@@ -39,6 +40,7 @@
 
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/cf_endian.h"
 #include "common/libs/utils/files.h"
 #include "common/libs/utils/size_utils.h"
 #include "common/libs/utils/subprocess.h"
@@ -47,13 +49,9 @@
 namespace cuttlefish {
 namespace {
 
-// Keep the full disk size a multiple of 64k, for crosvm's virtio_blk driver
-constexpr int DISK_SIZE_SHIFT = 16;
-
-// Keep all partitions 4k aligned, for host performance reasons
-constexpr int PARTITION_SIZE_SHIFT = 12;
-
 constexpr int GPT_NUM_PARTITIONS = 128;
+static const std::string CDISK_MAGIC = "composite_disk\x1d";
+static const std::string QCOW2_MAGIC = "QFI\xfb";
 
 /**
  * Creates a "Protective" MBR Partition Table header. The GUID
@@ -106,32 +104,53 @@
 struct __attribute__((packed)) GptBeginning {
   MasterBootRecord protective_mbr;
   GptHeader header;
-  std::uint8_t header_padding[420];
+  std::uint8_t header_padding[SECTOR_SIZE - sizeof(GptHeader)];
   GptPartitionEntry entries[GPT_NUM_PARTITIONS];
   std::uint8_t partition_alignment[3072];
 };
 
-static_assert(sizeof(GptBeginning) == SECTOR_SIZE * 40);
+static_assert(AlignToPowerOf2(sizeof(GptBeginning), PARTITION_SIZE_SHIFT) ==
+              sizeof(GptBeginning));
 
 struct __attribute__((packed)) GptEnd {
   GptPartitionEntry entries[GPT_NUM_PARTITIONS];
   GptHeader footer;
-  std::uint8_t footer_padding[420];
+  std::uint8_t footer_padding[SECTOR_SIZE - sizeof(GptHeader)];
 };
 
-static_assert(sizeof(GptEnd) == SECTOR_SIZE * 33);
+static_assert(sizeof(GptEnd) % SECTOR_SIZE == 0);
 
 struct PartitionInfo {
-  ImagePartition source;
-  std::uint64_t guest_size;
-  std::uint64_t host_size;
+  MultipleImagePartition source;
+  std::uint64_t size;
   std::uint64_t offset;
+
+  std::uint64_t AlignedSize() const { return AlignToPartitionSize(size); }
 };
 
+struct __attribute__((packed)) QCowHeader {
+  Be32 magic;
+  Be32 version;
+  Be64 backing_file_offset;
+  Be32 backing_file_size;
+  Be32 cluster_bits;
+  Be64 size;
+  Be32 crypt_method;
+  Be32 l1_size;
+  Be64 l1_table_offset;
+  Be64 refcount_table_offset;
+  Be32 refcount_table_clusters;
+  Be32 nb_snapshots;
+  Be64 snapshots_offset;
+};
+
+static_assert(sizeof(QCowHeader) == 72);
+
 /*
- * Returns the file size of `file_path`. If `file_path` is an Android-Sparse
- * file, returns the file size it would have after being converted to a raw
- * file.
+ * Returns the expanded file size of `file_path`. Note that the raw size of
+ * files doesn't match how large they may appear inside a VM.
+ *
+ * Supported types: Composite disk image, Qcows2, Android-Sparse, Raw
  *
  * Android-Sparse is a file format invented by Android that optimizes for
  * chunks of zeroes or repeated data. The Android build system can produce
@@ -139,15 +158,63 @@
  * disk file, as the imag eflashing process also can handle Android-Sparse
  * images.
  */
-std::uint64_t UnsparsedSize(const std::string& file_path) {
-  auto fd = open(file_path.c_str(), O_RDONLY);
-  CHECK(fd >= 0) << "Could not open \"" << file_path << "\""
-                 << strerror(errno);
-  auto sparse = sparse_file_import(fd, /* verbose */ false, /* crc */ false);
-  auto size =
-      sparse ? sparse_file_len(sparse, false, true) : FileSize(file_path);
-  close(fd);
-  return size;
+std::uint64_t ExpandedStorageSize(const std::string& file_path) {
+  android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY));
+  CHECK(fd.get() >= 0) << "Could not open \"" << file_path << "\""
+                       << strerror(errno);
+
+  std::uint64_t file_size = FileSize(file_path);
+
+  // Try to read the disk in a nicely-aligned block size unless the whole file
+  // is smaller.
+  constexpr uint64_t MAGIC_BLOCK_SIZE = 4096;
+  std::string magic(std::min(file_size, MAGIC_BLOCK_SIZE), '\0');
+  if (!android::base::ReadFully(fd, magic.data(), magic.size())) {
+    PLOG(FATAL) << "Fail to read: " << file_path;
+    return 0;
+  }
+  CHECK(lseek(fd, 0, SEEK_SET) != -1)
+      << "Fail to seek(\"" << file_path << "\")" << strerror(errno);
+
+  // Composite disk image
+  if (android::base::StartsWith(magic, CDISK_MAGIC)) {
+    // seek to the beginning of proto message
+    CHECK(lseek(fd, CDISK_MAGIC.size(), SEEK_SET) != -1)
+        << "Fail to seek(\"" << file_path << "\")" << strerror(errno);
+    std::string message;
+    if (!android::base::ReadFdToString(fd, &message)) {
+      PLOG(FATAL) << "Fail to read(cdisk): " << file_path;
+      return 0;
+    }
+    CompositeDisk cdisk;
+    if (!cdisk.ParseFromString(message)) {
+      PLOG(FATAL) << "Fail to parse(cdisk): " << file_path;
+      return 0;
+    }
+    return cdisk.length();
+  }
+
+  // Qcow2 image
+  if (android::base::StartsWith(magic, QCOW2_MAGIC)) {
+    QCowHeader header;
+    if (!android::base::ReadFully(fd, &header, sizeof(QCowHeader))) {
+      PLOG(FATAL) << "Fail to read(qcow2 header): " << file_path;
+      return 0;
+    }
+    return header.size.as_uint64_t();
+  }
+
+  // Android-Sparse
+  if (auto sparse =
+          sparse_file_import(fd, /* verbose */ false, /* crc */ false);
+      sparse) {
+    auto size = sparse_file_len(sparse, false, true);
+    sparse_file_destroy(sparse);
+    return size;
+  }
+
+  // raw image file
+  return file_size;
 }
 
 /*
@@ -165,6 +232,15 @@
   }
 }
 
+MultipleImagePartition ToMultipleImagePartition(ImagePartition source) {
+  return MultipleImagePartition{
+      .label = source.label,
+      .image_file_paths = std::vector{source.image_file_path},
+      .type = source.type,
+      .read_only = source.read_only,
+  };
+}
+
 /**
  * Incremental builder class for producing partition tables. Add partitions
  * one-by-one, then produce specification files
@@ -174,7 +250,7 @@
   std::vector<PartitionInfo> partitions_;
   std::uint64_t next_disk_offset_;
 
-  static const char *GetPartitionGUID(ImagePartition source) {
+  static const char* GetPartitionGUID(MultipleImagePartition source) {
     // Due to some endianness mismatch in e2fsprogs GUID vs GPT, the GUIDs are
     // rearranged to make the right GUIDs appear in gdisk
     switch (source.type) {
@@ -188,28 +264,33 @@
         LOG(FATAL) << "Unknown partition type: " << (int) source.type;
     }
   }
+
 public:
   CompositeDiskBuilder() : next_disk_offset_(sizeof(GptBeginning)) {}
 
-  void AppendDisk(ImagePartition source) {
-    auto host_size = UnsparsedSize(source.image_file_path);
-    auto guest_size = AlignToPowerOf2(host_size, PARTITION_SIZE_SHIFT);
-    CHECK(host_size == guest_size || source.read_only)
-        << "read-write file " << source.image_file_path
+  void AppendPartition(ImagePartition source) {
+    AppendPartition(ToMultipleImagePartition(source));
+  }
+
+  void AppendPartition(MultipleImagePartition source) {
+    uint64_t size = 0;
+    for (const auto& path : source.image_file_paths) {
+      size += ExpandedStorageSize(path);
+    }
+    auto aligned_size = AlignToPartitionSize(size);
+    CHECK(size == aligned_size || source.read_only)
+        << "read-write partition " << source.label
         << " is not aligned to the size of " << (1 << PARTITION_SIZE_SHIFT);
     partitions_.push_back(PartitionInfo{
         .source = source,
-        .guest_size = guest_size,
-        .host_size = host_size,
+        .size = size,
         .offset = next_disk_offset_,
     });
-    next_disk_offset_ =
-        AlignToPowerOf2(next_disk_offset_ + guest_size, PARTITION_SIZE_SHIFT);
+    next_disk_offset_ = next_disk_offset_ + aligned_size;
   }
 
   std::uint64_t DiskSize() const {
-    std::uint64_t val = next_disk_offset_ + sizeof(GptEnd);
-    return AlignToPowerOf2(val, DISK_SIZE_SHIFT);
+    return AlignToPowerOf2(next_disk_offset_ + sizeof(GptEnd), DISK_SIZE_SHIFT);
   }
 
   /**
@@ -228,22 +309,27 @@
     header->set_offset(0);
 
     for (auto& partition : partitions_) {
-      ComponentDisk* component = disk.add_component_disks();
-      component->set_file_path(AbsolutePath(partition.source.image_file_path));
-      component->set_offset(partition.offset);
-      component->set_read_write_capability(
-          partition.source.read_only ? ReadWriteCapability::READ_ONLY
-                                     : ReadWriteCapability::READ_WRITE);
-      // When partition's size differs from its size on the host
+      uint64_t size = 0;
+      for (const auto& path : partition.source.image_file_paths) {
+        ComponentDisk* component = disk.add_component_disks();
+        component->set_file_path(AbsolutePath(path));
+        component->set_offset(partition.offset + size);
+        component->set_read_write_capability(
+            partition.source.read_only ? ReadWriteCapability::READ_ONLY
+                                       : ReadWriteCapability::READ_WRITE);
+        size += ExpandedStorageSize(path);
+      }
+      CHECK(partition.size == size);
+      // When partition's aligned size differs from its (unaligned) size
       // reading the disk within the guest os would fail due to the gap.
       // Putting any disk bigger than 4K can fill this gap.
       // Here we reuse the header which is always > 4K.
       // We don't fill the "writable" disk's hole and it should be an error
       // because writes in the guest of can't be reflected to the backing file.
-      if (partition.guest_size != partition.host_size) {
+      if (partition.AlignedSize() != partition.size) {
         ComponentDisk* component = disk.add_component_disks();
         component->set_file_path(AbsolutePath(header_file));
-        component->set_offset(partition.offset + partition.host_size);
+        component->set_offset(partition.offset + partition.size);
         component->set_read_write_capability(ReadWriteCapability::READ_ONLY);
       }
     }
@@ -268,19 +354,20 @@
       return {};
     }
     GptBeginning gpt = {
-      .protective_mbr = ProtectiveMbr(DiskSize()),
-      .header = {
-        .signature = {'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'},
-        .revision = {0, 0, 1, 0},
-        .header_size = sizeof(GptHeader),
-        .current_lba = 1,
-        .backup_lba = (next_disk_offset_ + sizeof(GptEnd)) / SECTOR_SIZE - 1,
-        .first_usable_lba = sizeof(GptBeginning) / SECTOR_SIZE,
-        .last_usable_lba = (next_disk_offset_ - SECTOR_SIZE) / SECTOR_SIZE,
-        .partition_entries_lba = 2,
-        .num_partition_entries = GPT_NUM_PARTITIONS,
-        .partition_entry_size = sizeof(GptPartitionEntry),
-      },
+        .protective_mbr = ProtectiveMbr(DiskSize()),
+        .header =
+            {
+                .signature = {'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'},
+                .revision = {0, 0, 1, 0},
+                .header_size = sizeof(GptHeader),
+                .current_lba = 1,
+                .backup_lba = (DiskSize() / SECTOR_SIZE) - 1,
+                .first_usable_lba = sizeof(GptBeginning) / SECTOR_SIZE,
+                .last_usable_lba = (next_disk_offset_ / SECTOR_SIZE) - 1,
+                .partition_entries_lba = 2,
+                .num_partition_entries = GPT_NUM_PARTITIONS,
+                .partition_entry_size = sizeof(GptPartitionEntry),
+            },
     };
     uuid_generate(gpt.header.disk_guid);
     for (std::size_t i = 0; i < partitions_.size(); i++) {
@@ -288,7 +375,7 @@
       gpt.entries[i] = GptPartitionEntry{
           .first_lba = partition.offset / SECTOR_SIZE,
           .last_lba =
-              (partition.offset + partition.guest_size) / SECTOR_SIZE - 1,
+              (partition.offset + partition.AlignedSize()) / SECTOR_SIZE - 1,
       };
       uuid_generate(gpt.entries[i].unique_partition_guid);
       if (uuid_parse(GetPartitionGUID(partition.source),
@@ -314,9 +401,10 @@
    */
   GptEnd End(const GptBeginning& head) const {
     GptEnd gpt;
-    std::memcpy((void*) gpt.entries, (void*) head.entries, 128 * 128);
+    std::memcpy((void*)gpt.entries, (void*)head.entries, sizeof(gpt.entries));
     gpt.footer = head.header;
-    gpt.footer.partition_entries_lba = next_disk_offset_ / SECTOR_SIZE;
+    gpt.footer.partition_entries_lba =
+        (DiskSize() - sizeof(gpt.entries)) / SECTOR_SIZE - 1;
     std::swap(gpt.footer.current_lba, gpt.footer.backup_lba);
     gpt.footer.header_crc32 = 0;
     gpt.footer.header_crc32 =
@@ -334,11 +422,17 @@
   return true;
 }
 
-bool WriteEnd(SharedFD out, const GptEnd& end, std::int64_t padding) {
-  std::string end_str((const char*) &end, sizeof(GptEnd));
-  end_str.resize(end_str.size() + padding, '\0');
-  if (WriteAll(out, end_str) != end_str.size()) {
-    LOG(ERROR) << "Could not write GPT end: " << out->StrError();
+bool WriteEnd(SharedFD out, const GptEnd& end) {
+  auto disk_size = (end.footer.current_lba + 1) * SECTOR_SIZE;
+  auto footer_start = (end.footer.last_usable_lba + 1) * SECTOR_SIZE;
+  auto padding = disk_size - footer_start - sizeof(GptEnd);
+  std::string padding_str(padding, '\0');
+  if (WriteAll(out, padding_str) != padding_str.size()) {
+    LOG(ERROR) << "Could not write GPT end padding: " << out->StrError();
+    return false;
+  }
+  if (WriteAllBinary(out, &end) != sizeof(end)) {
+    LOG(ERROR) << "Could not write GPT end contents: " << out->StrError();
     return false;
   }
   return true;
@@ -394,12 +488,16 @@
 
 } // namespace
 
+uint64_t AlignToPartitionSize(uint64_t size) {
+  return AlignToPowerOf2(size, PARTITION_SIZE_SHIFT);
+}
+
 void AggregateImage(const std::vector<ImagePartition>& partitions,
                     const std::string& output_path) {
   DeAndroidSparse(partitions);
   CompositeDiskBuilder builder;
-  for (auto& disk : partitions) {
-    builder.AppendDisk(disk);
+  for (auto& partition : partitions) {
+    builder.AppendPartition(partition);
   }
   auto output = SharedFD::Creat(output_path, 0600);
   auto beginning = builder.Beginning();
@@ -415,8 +513,7 @@
                  << "\" to \"" << output_path << "\": " << output->StrError();
     }
     // Handle disk images that are not aligned to PARTITION_SIZE_SHIFT
-    std::uint64_t padding =
-        AlignToPowerOf2(file_size, PARTITION_SIZE_SHIFT) - file_size;
+    std::uint64_t padding = AlignToPartitionSize(file_size) - file_size;
     std::string padding_str;
     padding_str.resize(padding, '\0');
     if (WriteAll(output, padding_str) != padding_str.size()) {
@@ -424,9 +521,7 @@
                  << "\": " << output->StrError();
     }
   }
-  std::uint64_t padding =
-      builder.DiskSize() - ((beginning.header.backup_lba + 1) * SECTOR_SIZE);
-  if (!WriteEnd(output, builder.End(beginning), padding)) {
+  if (!WriteEnd(output, builder.End(beginning))) {
     LOG(FATAL) << "Could not write GPT end to \"" << output_path
                << "\": " << output->StrError();
   }
@@ -436,9 +531,21 @@
                          const std::string& header_file,
                          const std::string& footer_file,
                          const std::string& output_composite_path) {
+  std::vector<MultipleImagePartition> multiple_image_partitions;
+  for (const auto& partition : partitions) {
+    multiple_image_partitions.push_back(ToMultipleImagePartition(partition));
+  }
+  return CreateCompositeDisk(std::move(multiple_image_partitions), header_file,
+                             footer_file, output_composite_path);
+}
+
+void CreateCompositeDisk(std::vector<MultipleImagePartition> partitions,
+                         const std::string& header_file,
+                         const std::string& footer_file,
+                         const std::string& output_composite_path) {
   CompositeDiskBuilder builder;
-  for (auto& disk : partitions) {
-    builder.AppendDisk(disk);
+  for (auto& partition : partitions) {
+    builder.AppendPartition(partition);
   }
   auto header = SharedFD::Creat(header_file, 0600);
   auto beginning = builder.Beginning();
@@ -447,16 +554,14 @@
                << "\": " << header->StrError();
   }
   auto footer = SharedFD::Creat(footer_file, 0600);
-  std::uint64_t padding =
-      builder.DiskSize() - ((beginning.header.backup_lba + 1) * SECTOR_SIZE);
-  if (!WriteEnd(footer, builder.End(beginning), padding)) {
+  if (!WriteEnd(footer, builder.End(beginning))) {
     LOG(FATAL) << "Could not write GPT end to \"" << footer_file
                << "\": " << footer->StrError();
   }
   auto composite_proto = builder.MakeCompositeDiskSpec(header_file, footer_file);
   std::ofstream composite(output_composite_path.c_str(),
                           std::ios::binary | std::ios::trunc);
-  composite << "composite_disk\x1d";
+  composite << CDISK_MAGIC;
   composite_proto.SerializeToOstream(&composite);
   composite.flush();
 }
diff --git a/host/libs/image_aggregator/image_aggregator.h b/host/libs/image_aggregator/image_aggregator.h
index 97bc13b..b9a2458 100644
--- a/host/libs/image_aggregator/image_aggregator.h
+++ b/host/libs/image_aggregator/image_aggregator.h
@@ -36,6 +36,15 @@
   bool read_only;
 };
 
+struct MultipleImagePartition {
+  std::string label;
+  std::vector<std::string> image_file_paths;
+  ImagePartitionType type;
+  bool read_only;
+};
+
+uint64_t AlignToPartitionSize(uint64_t size);
+
 /**
  * Combine the files in `partition` into a single raw disk file and write it to
  * `output_path`. The raw disk file will have a GUID Partition Table and copy in
@@ -62,6 +71,14 @@
                          const std::string& output_composite_path);
 
 /**
+ * Overloaded function to generate a composite disk with multiple images for a
+ * single partition.
+ */
+void CreateCompositeDisk(std::vector<MultipleImagePartition> partitions,
+                         const std::string& header_file,
+                         const std::string& footer_file,
+                         const std::string& output_composite_path);
+/**
  * Generate a qcow overlay backed by a given implementation file.
  *
  * qcow, or "QEMU Copy-On-Write" is a file format containing a list of disk
diff --git a/host/libs/screen_connector/Android.bp b/host/libs/screen_connector/Android.bp
index fcfc747..d4e4002 100644
--- a/host/libs/screen_connector/Android.bp
+++ b/host/libs/screen_connector/Android.bp
@@ -28,10 +28,18 @@
         "libjsoncpp",
         "liblog",
     ],
+    header_libs: [
+        "libcuttlefish_confui_host_headers",
+    ],
     static_libs: [
         "libcuttlefish_host_config",
         "libcuttlefish_utils",
+        "libcuttlefish_confui",
         "libcuttlefish_wayland_server",
+        "libcuttlefish_confui_host",
+        "libft2.nodep",
+        "libteeui",
+        "libteeui_localization",
     ],
     defaults: ["cuttlefish_host"],
 }
diff --git a/host/libs/screen_connector/screen_connector.h b/host/libs/screen_connector/screen_connector.h
index 86aa523..6b1c039 100644
--- a/host/libs/screen_connector/screen_connector.h
+++ b/host/libs/screen_connector/screen_connector.h
@@ -16,21 +16,26 @@
 
 #pragma once
 
-#include <cassert>
 #include <cstdint>
 #include <functional>
 #include <memory>
 #include <mutex>
+#include <optional>
+#include <string>
 #include <thread>
 #include <type_traits>
 
 #include <android-base/logging.h>
-
+#include "common/libs/confui/confui.h"
+#include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/size_utils.h"
+
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/confui/host_mode_ctrl.h"
+#include "host/libs/confui/host_utils.h"
 #include "host/libs/screen_connector/screen_connector_common.h"
+#include "host/libs/screen_connector/screen_connector_multiplexer.h"
 #include "host/libs/screen_connector/screen_connector_queue.h"
-#include "host/libs/screen_connector/screen_connector_ctrl.h"
 #include "host/libs/screen_connector/wayland_screen_connector.h"
 
 namespace cuttlefish {
@@ -44,6 +49,8 @@
   static_assert(std::is_base_of<ScreenConnectorFrameInfo, ProcessedFrameType>::value,
                 "ProcessedFrameType should inherit ScreenConnectorFrameInfo");
 
+  using FrameMultiplexer = ScreenConnectorInputMultiplexer<ProcessedFrameType>;
+
   /**
    * This is the type of the callback function WebRTC/VNC is supposed to provide
    * ScreenConnector with.
@@ -57,18 +64,21 @@
    * call.
    */
   using GenerateProcessedFrameCallback = std::function<void(
-      std::uint32_t /*display_number*/, std::uint8_t* /*frame_pixels*/,
+      std::uint32_t /*display_number*/, std::uint32_t /*frame_width*/,
+      std::uint32_t /*frame_height*/, std::uint32_t /*frame_stride_bytes*/,
+      std::uint8_t* /*frame_bytes*/,
       /* ScImpl enqueues this type into the Q */
       ProcessedFrameType& msg)>;
 
-  static std::unique_ptr<ScreenConnector<ProcessedFrameType>> Get(const int frames_fd) {
+  static std::unique_ptr<ScreenConnector<ProcessedFrameType>> Get(
+      const int frames_fd, HostModeCtrl& host_mode_ctrl) {
     auto config = cuttlefish::CuttlefishConfig::Get();
     ScreenConnector<ProcessedFrameType>* raw_ptr = nullptr;
     if (config->gpu_mode() == cuttlefish::kGpuModeDrmVirgl ||
         config->gpu_mode() == cuttlefish::kGpuModeGfxStream ||
         config->gpu_mode() == cuttlefish::kGpuModeGuestSwiftshader) {
       raw_ptr = new ScreenConnector<ProcessedFrameType>(
-          std::make_unique<WaylandScreenConnector>(frames_fd));
+          std::make_unique<WaylandScreenConnector>(frames_fd), host_mode_ctrl);
     } else {
       LOG(FATAL) << "Invalid gpu mode: " << config->gpu_mode();
     }
@@ -85,15 +95,28 @@
   void SetCallback(GenerateProcessedFrameCallback&& frame_callback) {
     std::lock_guard<std::mutex> lock(streamer_callback_mutex_);
     callback_from_streamer_ = std::move(frame_callback);
-    /*
-     * the first WaitForAtLeastOneClientConnection() call from VNC requires the
-     * Android-frame-processing thread starts beforehands (b/178504150)
-     */
-    if (!is_frame_fetching_thread_started_) {
-      is_frame_fetching_thread_started_ = true;
-      sc_android_impl_fetcher_ =
-          std::move(std::thread(&ScreenConnector::FrameFetchingLoop, this));
-    }
+    streamer_callback_set_cv_.notify_all();
+
+    sc_android_src_->SetFrameCallback(
+        [this](std::uint32_t display_number, std::uint32_t frame_w,
+               std::uint32_t frame_h, std::uint32_t frame_stride_bytes,
+               std::uint8_t* frame_bytes) {
+          const bool is_confui_mode = host_mode_ctrl_.IsConfirmatioUiMode();
+          if (is_confui_mode) {
+            return;
+          }
+
+          ProcessedFrameType processed_frame;
+
+          {
+            std::lock_guard<std::mutex> lock(streamer_callback_mutex_);
+            callback_from_streamer_(display_number, frame_w, frame_h,
+                                    frame_stride_bytes, frame_bytes,
+                                    processed_frame);
+          }
+
+          sc_frame_multiplexer_.PushToAndroidQueue(std::move(processed_frame));
+        });
   }
 
   bool IsCallbackSet() const override {
@@ -108,42 +131,7 @@
    *
    * NOTE THAT THIS IS THE ONLY CONSUMER OF THE TWO QUEUES
    */
-  ProcessedFrameType OnNextFrame() {
-    // sc_ctrl has a semaphore internally
-    // passing beyond SemWait means either queue has an item
-    sc_ctrl_.SemWait();
-    return sc_android_queue_.PopFront();
-
-    // TODO: add confirmation ui
-    /*
-     * if (!sc_android_queue_.Empty()) return sc_android_queue_.PopFront();
-     * else return conf_ui_queue.PopFront();
-     */
-  }
-
-  [[noreturn]] void FrameFetchingLoop() {
-    while (true) {
-      sc_ctrl_.WaitAndroidMode( /* pass method to stop sc_android_impl_ */);
-      /*
-       * TODO: instead of WaitAndroidMode,
-       * we could sc_android_impl_->OnFrameAfter but enqueue it only in AndroidMode
-       */
-      ProcessedFrameType msg;
-      decltype(callback_from_streamer_) cp_of_streamer_callback;
-      {
-        std::lock_guard<std::mutex> lock(streamer_callback_mutex_);
-        cp_of_streamer_callback = callback_from_streamer_;
-      }
-      GenerateProcessedFrameCallbackImpl callback_for_sc_impl =
-          std::bind(cp_of_streamer_callback,
-                    std::placeholders::_1, std::placeholders::_2,
-                    std::ref(msg));
-      bool flag = sc_android_impl_->OnNextFrame(callback_for_sc_impl);
-      msg.is_success_ = flag && msg.is_success_;
-      auto result = ProcessedFrameType{std::move(msg)};
-      sc_android_queue_.PushBack(std::move(result));
-    }
-  }
+  ProcessedFrameType OnNextFrame() { return sc_frame_multiplexer_.Pop(); }
 
   /**
    * ConfUi calls this when it has frames to render
@@ -152,14 +140,34 @@
    * Android guest frames if Confirmation UI HAL is not active.
    *
    */
-  bool RenderConfirmationUi(const std::uint32_t, std::uint8_t*) override {
+  bool RenderConfirmationUi(std::uint32_t display_number,
+                            std::uint32_t frame_width,
+                            std::uint32_t frame_height,
+                            std::uint32_t frame_stride_bytes,
+                            std::uint8_t* frame_bytes) override {
+    render_confui_cnt_++;
+    // wait callback is not set, the streamer is not ready
+    // return with LOG(ERROR)
+    if (!IsCallbackSet()) {
+      ConfUiLog(ERROR) << "callback function to process frames is not yet set";
+      return false;
+    }
+    ProcessedFrameType processed_frame;
+    auto this_thread_name = cuttlefish::confui::thread::GetName();
+    ConfUiLog(DEBUG) << this_thread_name
+                     << "is sending a #" + std::to_string(render_confui_cnt_)
+                     << "Conf UI frame";
+    callback_from_streamer_(display_number, frame_width, frame_height,
+                            frame_stride_bytes, frame_bytes, processed_frame);
+    // now add processed_frame to the queue
+    sc_frame_multiplexer_.PushToConfUiQueue(std::move(processed_frame));
     return true;
   }
 
   // Let the screen connector know when there are clients connected
   void ReportClientsConnected(bool have_clients) {
     // screen connector implementation must implement ReportClientsConnected
-    sc_android_impl_->ReportClientsConnected(have_clients);
+    sc_android_src_->ReportClientsConnected(have_clients);
     return ;
   }
 
@@ -167,20 +175,30 @@
   template <typename T,
             typename = std::enable_if_t<
                 std::is_base_of<ScreenConnectorSource, T>::value, void>>
-  ScreenConnector(std::unique_ptr<T>&& impl)
-      : sc_android_impl_{std::move(impl)},
-        is_frame_fetching_thread_started_(false),
-        sc_android_queue_(sc_ctrl_) {}
+  ScreenConnector(std::unique_ptr<T>&& impl, HostModeCtrl& host_mode_ctrl)
+      : sc_android_src_{std::move(impl)},
+        host_mode_ctrl_{host_mode_ctrl},
+        on_next_frame_cnt_{0},
+        render_confui_cnt_{0},
+        sc_frame_multiplexer_{host_mode_ctrl_} {}
   ScreenConnector() = delete;
 
  private:
-  std::unique_ptr<ScreenConnectorSource> sc_android_impl_; // either socket_based or wayland
-  bool is_frame_fetching_thread_started_;
-  ScreenConnectorCtrl sc_ctrl_;
-  ScreenConnectorQueue<ProcessedFrameType> sc_android_queue_;
+  // either socket_based or wayland
+  std::unique_ptr<ScreenConnectorSource> sc_android_src_;
+  HostModeCtrl& host_mode_ctrl_;
+  unsigned long long int on_next_frame_cnt_;
+  unsigned long long int render_confui_cnt_;
+  /**
+   * internally has conf ui & android queues.
+   *
+   * multiplexting the two input queues, so the consumer gets one input
+   * at a time from the right queue
+   */
+  FrameMultiplexer sc_frame_multiplexer_;
   GenerateProcessedFrameCallback callback_from_streamer_;
-  std::thread sc_android_impl_fetcher_;
   std::mutex streamer_callback_mutex_; // mutex to set & read callback_from_streamer_
+  std::condition_variable streamer_callback_set_cv_;
 };
 
 }  // namespace cuttlefish
diff --git a/host/libs/screen_connector/screen_connector_common.h b/host/libs/screen_connector/screen_connector_common.h
index 48c7c76..1df6d86 100644
--- a/host/libs/screen_connector/screen_connector_common.h
+++ b/host/libs/screen_connector/screen_connector_common.h
@@ -34,16 +34,20 @@
 };
 
 // this callback type is going directly to socket-based or wayland ScreenConnector
-using GenerateProcessedFrameCallbackImpl = std::function<void(std::uint32_t /*display_number*/,
-                                                              std::uint8_t* /*frame_pixels*/)>;
+using GenerateProcessedFrameCallbackImpl =
+    std::function<void(std::uint32_t /*display_number*/,      //
+                       std::uint32_t /*frame_width*/,         //
+                       std::uint32_t /*frame_height*/,        //
+                       std::uint32_t /*frame_stride_bytes*/,  //
+                       std::uint8_t* /*frame_pixels*/)>;
 
 class ScreenConnectorSource {
  public:
   virtual ~ScreenConnectorSource() = default;
   // Runs the given callback on the next available frame after the given
   // frame number and returns true if successful.
-  virtual bool OnNextFrame(
-      const GenerateProcessedFrameCallbackImpl& frame_callback) = 0;
+  virtual void SetFrameCallback(
+      GenerateProcessedFrameCallbackImpl frame_callback) = 0;
   virtual void ReportClientsConnected(bool /*have_clients*/) { /* ignore by default */ }
   ScreenConnectorSource() = default;
 };
@@ -83,8 +87,11 @@
 };
 
 struct ScreenConnectorFrameRenderer {
-  virtual bool RenderConfirmationUi(const std::uint32_t display,
-                                    std::uint8_t* raw_frame) = 0;
+  virtual bool RenderConfirmationUi(std::uint32_t display_number,
+                                    std::uint32_t frame_width,
+                                    std::uint32_t frame_height,
+                                    std::uint32_t frame_stride_bytes,
+                                    std::uint8_t* frame_bytes) = 0;
   virtual bool IsCallbackSet() const = 0;
   virtual ~ScreenConnectorFrameRenderer() = default;
 };
diff --git a/host/libs/screen_connector/screen_connector_multiplexer.h b/host/libs/screen_connector/screen_connector_multiplexer.h
new file mode 100644
index 0000000..b620531
--- /dev/null
+++ b/host/libs/screen_connector/screen_connector_multiplexer.h
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include "common/libs/concurrency/multiplexer.h"
+#include "common/libs/confui/confui.h"
+
+#include "host/libs/confui/host_mode_ctrl.h"
+#include "host/libs/screen_connector/screen_connector_queue.h"
+
+namespace cuttlefish {
+template <typename ProcessedFrameType>
+class ScreenConnectorInputMultiplexer {
+  using Queue = ScreenConnectorQueue<ProcessedFrameType>;
+  using Multiplexer = Multiplexer<ProcessedFrameType, Queue>;
+
+ public:
+  ScreenConnectorInputMultiplexer(HostModeCtrl& host_mode_ctrl)
+      : host_mode_ctrl_(host_mode_ctrl) {
+    sc_android_queue_id_ =
+        multiplexer_.RegisterQueue(multiplexer_.CreateQueue(/* q size */ 2));
+    sc_confui_queue_id_ =
+        multiplexer_.RegisterQueue(multiplexer_.CreateQueue(/* q size */ 2));
+  }
+
+  virtual ~ScreenConnectorInputMultiplexer() = default;
+
+  void PushToAndroidQueue(ProcessedFrameType&& t) {
+    multiplexer_.Push(sc_android_queue_id_, std::move(t));
+  }
+
+  void PushToConfUiQueue(ProcessedFrameType&& t) {
+    multiplexer_.Push(sc_confui_queue_id_, std::move(t));
+  }
+
+  // customize Pop()
+  ProcessedFrameType Pop() {
+    on_next_frame_cnt_++;
+
+    // is_discard_frame is thread-specific
+    bool is_discard_frame = false;
+
+    // callback to select the queue index, and update is_discard_frame
+    auto selector = [this, &is_discard_frame]() -> int {
+      if (multiplexer_.IsEmpty(sc_android_queue_id_)) {
+        ConfUiLog(VERBOSE)
+            << "Streamer gets Conf UI frame with host ctrl mode = "
+            << static_cast<std::uint32_t>(host_mode_ctrl_.GetMode())
+            << " and cnd = #" << on_next_frame_cnt_;
+        return sc_confui_queue_id_;
+      }
+      auto mode = host_mode_ctrl_.GetMode();
+      if (mode != HostModeCtrl::ModeType::kAndroidMode) {
+        // AndroidFrameFetchingLoop could have added 1 or 2 frames
+        // before it becomes Conf UI mode.
+        ConfUiLog(VERBOSE)
+            << "Streamer ignores Android frame with host ctrl mode ="
+            << static_cast<std::uint32_t>(mode) << "and cnd = #"
+            << on_next_frame_cnt_;
+        is_discard_frame = true;
+      }
+      ConfUiLog(VERBOSE) << "Streamer gets Android frame with host ctrl mode ="
+                         << static_cast<std::uint32_t>(mode) << "and cnd = #"
+                         << on_next_frame_cnt_;
+      return sc_android_queue_id_;
+    };
+
+    while (true) {
+      ConfUiLog(VERBOSE) << "Streamer waiting Semaphore with host ctrl mode ="
+                         << static_cast<std::uint32_t>(
+                                host_mode_ctrl_.GetMode())
+                         << " and cnd = #" << on_next_frame_cnt_;
+      auto processed_frame = multiplexer_.Pop(selector);
+      if (!is_discard_frame) {
+        return processed_frame;
+      }
+      is_discard_frame = false;
+    }
+  }
+
+ private:
+  HostModeCtrl& host_mode_ctrl_;
+  Multiplexer multiplexer_;
+  unsigned long long int on_next_frame_cnt_;
+  int sc_android_queue_id_;
+  int sc_confui_queue_id_;
+};
+}  // end of namespace cuttlefish
diff --git a/host/libs/screen_connector/screen_connector_queue.h b/host/libs/screen_connector/screen_connector_queue.h
index 26c60c9..7643800 100644
--- a/host/libs/screen_connector/screen_connector_queue.h
+++ b/host/libs/screen_connector/screen_connector_queue.h
@@ -16,14 +16,13 @@
 
 #pragma once
 
+#include <condition_variable>
 #include <deque>
 #include <memory>
-#include <thread>
 #include <mutex>
-#include <condition_variable>
-#include <chrono>
+#include <thread>
 
-#include "host/libs/screen_connector/screen_connector_ctrl.h"
+#include "common/libs/concurrency/semaphore.h"
 
 namespace cuttlefish {
 // move-based concurrent queue
@@ -31,21 +30,17 @@
 class ScreenConnectorQueue {
 
  public:
-  static const int kQSize = 2;
-
   static_assert( is_movable<T>::value,
                  "Items in ScreenConnectorQueue should be std::mov-able");
 
-  ScreenConnectorQueue(ScreenConnectorCtrl& sc_ctrl_)
-      : q_mutex_(std::make_unique<std::mutex>()),
-        global_item_tracker_(sc_ctrl_)
-  {}
+  ScreenConnectorQueue(const int q_max_size = 2)
+      : q_mutex_(std::make_unique<std::mutex>()), q_max_size_{q_max_size} {}
   ScreenConnectorQueue(ScreenConnectorQueue&& cq) = delete;
   ScreenConnectorQueue(const ScreenConnectorQueue& cq) = delete;
   ScreenConnectorQueue& operator=(const ScreenConnectorQueue& cq) = delete;
   ScreenConnectorQueue& operator=(ScreenConnectorQueue&& cq) = delete;
 
-  bool Empty() const {
+  bool IsEmpty() const {
     const std::lock_guard<std::mutex> lock(*q_mutex_);
     return buffer_.empty();
   }
@@ -62,9 +57,9 @@
   }
 
   /*
-   * PushBack( std::move(src) );
+   * Push( std::move(src) );
    *
-   * Note: this queue is suppoed to be used only by ScreenConnector-
+   * Note: this queue is supposed to be used only by ScreenConnector-
    * related components such as ScreenConnectorSource
    *
    * The traditional assumption was that when webRTC or VNC calls
@@ -78,7 +73,7 @@
    * should stop adding itmes to the queue.
    *
    */
-  void PushBack(T&& item) {
+  void Push(T&& item) {
     std::unique_lock<std::mutex> lock(*q_mutex_);
     if (Full()) {
       auto is_empty =
@@ -86,23 +81,11 @@
       q_empty_.wait(lock, is_empty);
     }
     buffer_.push_back(std::move(item));
-    /* Whether the total number of items in ALL queus is 0 or not
-     * is tracked via a semaphore shared by all queues
-     *
-     * This is NOT intended to block queue from pushing an item
-     * This IS intended to awake the screen_connector consumer thread
-     * when one or more items are available at least in one queue
-     */
-    global_item_tracker_.SemPost();
   }
-  void PushBack(T& item) = delete;
-  void PushBack(const T& item) = delete;
+  void Push(T& item) = delete;
+  void Push(const T& item) = delete;
 
-  /*
-   * PopFront must be preceded by global_item_tracker_.SemWaitItem()
-   *
-   */
-  T PopFront() {
+  T Pop() {
     const std::lock_guard<std::mutex> lock(*q_mutex_);
     auto item = std::move(buffer_.front());
     buffer_.pop_front();
@@ -116,12 +99,12 @@
   bool Full() const {
     // call this in a critical section
     // after acquiring q_mutex_
-    return kQSize == buffer_.size();
+    return q_max_size_ == buffer_.size();
   }
   std::deque<T> buffer_;
   std::unique_ptr<std::mutex> q_mutex_;
   std::condition_variable q_empty_;
-  ScreenConnectorCtrl& global_item_tracker_;
+  const int q_max_size_;
 };
 
 } // namespace cuttlefish
diff --git a/host/libs/screen_connector/wayland_screen_connector.cpp b/host/libs/screen_connector/wayland_screen_connector.cpp
index 7eb6642..dbd4052 100644
--- a/host/libs/screen_connector/wayland_screen_connector.cpp
+++ b/host/libs/screen_connector/wayland_screen_connector.cpp
@@ -33,10 +33,9 @@
   server_.reset(new wayland::WaylandServer(wayland_fd));
 }
 
-bool WaylandScreenConnector::OnNextFrame(
-    const GenerateProcessedFrameCallbackImpl& frame_callback) {
-  server_->OnNextFrame(frame_callback);
-  return true;
+void WaylandScreenConnector::SetFrameCallback(
+    GenerateProcessedFrameCallbackImpl frame_callback) {
+  server_->SetFrameCallback(std::move(frame_callback));
 }
 
 }  // namespace cuttlefish
diff --git a/host/libs/screen_connector/wayland_screen_connector.h b/host/libs/screen_connector/wayland_screen_connector.h
index 00e7ca0..36ed322 100644
--- a/host/libs/screen_connector/wayland_screen_connector.h
+++ b/host/libs/screen_connector/wayland_screen_connector.h
@@ -28,8 +28,8 @@
  public:
   WaylandScreenConnector(int frames_fd);
 
-  bool OnNextFrame(
-      const GenerateProcessedFrameCallbackImpl& frame_callback) override;
+  void SetFrameCallback(
+      GenerateProcessedFrameCallbackImpl frame_callback) override;
 
  private:
   std::unique_ptr<wayland::WaylandServer> server_;
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index 4f136fd..b441977 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -40,9 +40,10 @@
 
 namespace {
 
-std::string GetControlSocketPath(const CuttlefishConfig& config) {
-  return config.ForDefaultInstance()
-      .PerInstanceInternalPath("crosvm_control.sock");
+std::string GetControlSocketPath(const CuttlefishConfig& config,
+                                 const std::string& socket_name) {
+  return config.ForDefaultInstance().PerInstanceInternalPath(
+      socket_name.c_str());
 }
 
 SharedFD AddTapFdParameter(Command* crosvm_cmd,
@@ -77,11 +78,11 @@
   return success;
 }
 
-bool Stop() {
+bool Stop(const std::string& socket_name) {
   auto config = CuttlefishConfig::Get();
   Command command(config->crosvm_binary());
   command.AddParameter("stop");
-  command.AddParameter(GetControlSocketPath(*config));
+  command.AddParameter(GetControlSocketPath(*config, socket_name));
 
   auto process = command.Start();
 
@@ -106,7 +107,7 @@
   // HALs.
   if (gpu_mode == kGpuModeGuestSwiftshader) {
     return {
-        "androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_1),
+        "androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_2),
         "androidboot.hardware.gralloc=minigbm",
         "androidboot.hardware.hwcomposer=ranchu",
         "androidboot.hardware.egl=angle",
@@ -126,7 +127,7 @@
     return {
         "androidboot.cpuvulkan.version=0",
         "androidboot.hardware.gralloc=minigbm",
-        "androidboot.hardware.hwcomposer=drm_minigbm",
+        "androidboot.hardware.hwcomposer=ranchu",
         "androidboot.hardware.egl=emulation",
         "androidboot.hardware.vulkan=ranchu",
         "androidboot.hardware.gltransport=virtio-gpu-asg",
@@ -147,18 +148,35 @@
   }
 }
 
+constexpr auto crosvm_socket = "crosvm_control.sock";
+constexpr auto crosvm_for_ap_socket = "crosvm_for_ap_control.sock";
+
 std::vector<Command> CrosvmManager::StartCommands(
     const CuttlefishConfig& config) {
   auto instance = config.ForDefaultInstance();
   Command crosvm_cmd(config.crosvm_binary(), [](Subprocess* proc) {
-    auto stopped = Stop();
+    auto stopped = Stop(crosvm_socket);
     if (stopped) {
-      return true;
+      return StopperResult::kStopSuccess;
     }
     LOG(WARNING) << "Failed to stop VMM nicely, attempting to KILL";
-    return KillSubprocess(proc);
+    return KillSubprocess(proc) == StopperResult::kStopSuccess
+               ? StopperResult::kStopCrash
+               : StopperResult::kStopFailure;
   });
+  bool use_ap_instance =
+      !config.ap_rootfs_image().empty() && !config.ap_kernel_image().empty();
 
+  Command ap_cmd(config.crosvm_binary(), [](Subprocess* proc) {
+    auto stopped = Stop(crosvm_for_ap_socket);
+    if (stopped) {
+      return StopperResult::kStopSuccess;
+    }
+    LOG(WARNING) << "Failed to stop VMM for AP nicely, attempting to KILL";
+    return KillSubprocess(proc) == StopperResult::kStopSuccess
+               ? StopperResult::kStopCrash
+               : StopperResult::kStopFailure;
+  });
   int hvc_num = 0;
   int serial_num = 0;
   auto add_hvc_sink = [&crosvm_cmd, &hvc_num]() {
@@ -202,6 +220,7 @@
   };
 
   crosvm_cmd.AddParameter("run");
+  ap_cmd.AddParameter("run");
 
   if (!config.smt()) {
     crosvm_cmd.AddParameter("--no-smt");
@@ -211,6 +230,13 @@
     crosvm_cmd.AddParameter("--vhost-net");
   }
 
+  if (!config.vhost_user_mac80211_hwsim().empty()) {
+    crosvm_cmd.AddParameter("--vhost-user-mac80211-hwsim=",
+                            config.vhost_user_mac80211_hwsim());
+    ap_cmd.AddParameter("--vhost-user-mac80211-hwsim=",
+                        config.vhost_user_mac80211_hwsim());
+  }
+
   if (config.protected_vm()) {
     crosvm_cmd.AddParameter("--protected-vm");
   }
@@ -220,23 +246,20 @@
     crosvm_cmd.AddParameter("--gdb=", config.gdb_port());
   }
 
-  auto display_configs = config.display_configs();
-  CHECK_GE(display_configs.size(), 1);
-  auto display_config = display_configs[0];
-
   auto gpu_mode = config.gpu_mode();
-
   if (gpu_mode == kGpuModeGuestSwiftshader) {
-    crosvm_cmd.AddParameter("--gpu=2D,",
-                            "width=", display_config.width, ",",
-                            "height=", display_config.height);
+    crosvm_cmd.AddParameter("--gpu=2D");
   } else if (gpu_mode == kGpuModeDrmVirgl || gpu_mode == kGpuModeGfxStream) {
     crosvm_cmd.AddParameter(gpu_mode == kGpuModeGfxStream ?
                                 "--gpu=gfxstream," : "--gpu=",
-                            "width=", display_config.width, ",",
-                            "height=", display_config.height, ",",
                             "egl=true,surfaceless=true,glx=false,gles=true");
   }
+
+  for (const auto& display_config : config.display_configs()) {
+    crosvm_cmd.AddParameter("--gpu-display=", "width=", display_config.width,
+                            ",", "height=", display_config.height);
+  }
+
   crosvm_cmd.AddParameter("--wayland-sock=", instance.frames_socket_path());
 
   // crosvm_cmd.AddParameter("--null-audio");
@@ -251,23 +274,40 @@
     crosvm_cmd.AddParameter(config.protected_vm() ? "--disk=" :
                                                     "--rwdisk=", disk);
   }
-  crosvm_cmd.AddParameter("--socket=", GetControlSocketPath(config));
+  crosvm_cmd.AddParameter("--socket=",
+                          GetControlSocketPath(config, crosvm_socket));
+  ap_cmd.AddParameter("--socket=",
+                      GetControlSocketPath(config, crosvm_for_ap_socket));
 
   if (config.enable_vnc_server() || config.enable_webrtc()) {
     auto touch_type_parameter =
         config.enable_webrtc() ? "--multi-touch=" : "--single-touch=";
-    crosvm_cmd.AddParameter(touch_type_parameter, instance.touch_socket_path(),
-                            ":", display_config.width, ":",
-                            display_config.height);
+
+    auto display_configs = config.display_configs();
+    CHECK_GE(display_configs.size(), 1);
+
+    for (int i = 0; i < display_configs.size(); ++i) {
+      auto display_config = display_configs[i];
+
+      crosvm_cmd.AddParameter(touch_type_parameter,
+                              instance.touch_socket_path(i), ":",
+                              display_config.width, ":", display_config.height);
+    }
     crosvm_cmd.AddParameter("--keyboard=", instance.keyboard_socket_path());
   }
   if (config.enable_webrtc()) {
     crosvm_cmd.AddParameter("--switches=", instance.switches_socket_path());
   }
 
-  auto wifi_tap = AddTapFdParameter(&crosvm_cmd, instance.wifi_tap_name());
   AddTapFdParameter(&crosvm_cmd, instance.mobile_tap_name());
 
+  SharedFD wifi_tap;
+  if (config.vhost_user_mac80211_hwsim().empty()) {
+    wifi_tap = AddTapFdParameter(&crosvm_cmd, instance.wifi_tap_name());
+  } else if (use_ap_instance) {
+    wifi_tap = AddTapFdParameter(&ap_cmd, instance.wifi_tap_name());
+  }
+
   if (FileExists(instance.access_kregistry_path())) {
     crosvm_cmd.AddParameter("--rw-pmem-device=",
                             instance.access_kregistry_path());
@@ -289,8 +329,10 @@
       return {};
     }
     crosvm_cmd.AddParameter("--seccomp-policy-dir=", config.seccomp_policy_dir());
+    ap_cmd.AddParameter("--seccomp-policy-dir=", config.seccomp_policy_dir());
   } else {
     crosvm_cmd.AddParameter("--disable-sandbox");
+    ap_cmd.AddParameter("--disable-sandbox");
   }
 
   if (instance.vsock_guest_cid() >= 2) {
@@ -376,7 +418,7 @@
       << VmManager::kMaxDisks + VmManager::kDefaultNumHvcs << " devices";
 
   if (config.enable_audio()) {
-    crosvm_cmd.AddParameter("--ac97=backend=vios,capture=false,server=" +
+    crosvm_cmd.AddParameter("--sound=",
                             config.ForDefaultInstance().audio_server_path());
   }
 
@@ -399,7 +441,8 @@
   // Only run the leases workaround if we are not using the new network
   // bridge architecture - in that case, we have a wider DHCP address
   // space and stale leases should be much less of an issue
-  if (!FileExists("/var/run/cuttlefish-dnsmasq-cvd-wbr.leases")) {
+  if (!FileExists("/var/run/cuttlefish-dnsmasq-cvd-wbr.leases") &&
+      (config.vhost_user_mac80211_hwsim().empty() || use_ap_instance)) {
     // TODO(schuffelen): QEMU also needs this and this is not the best place for
     // this code. Find a better place to put it.
     auto lease_file =
@@ -409,9 +452,14 @@
                  << "network may not work.";
     }
   }
+  ap_cmd.AddParameter("--root=", config.ap_rootfs_image());
+  ap_cmd.AddParameter(config.ap_kernel_image());
 
   std::vector<Command> ret;
   ret.push_back(std::move(crosvm_cmd));
+  if (use_ap_instance) {
+    ret.push_back(std::move(ap_cmd));
+  }
   ret.push_back(std::move(log_tee_cmd));
   return ret;
 }
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index f26818a..2cd7e46 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -102,7 +102,7 @@
         "androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_1),
         "androidboot.hardware.gralloc=minigbm",
         "androidboot.hardware.hwcomposer=ranchu",
-        "androidboot.hardware.egl=swiftshader",
+        "androidboot.hardware.egl=angle",
         "androidboot.hardware.vulkan=pastel",
     };
   }
@@ -140,11 +140,13 @@
   auto stop = [](Subprocess* proc) {
     auto stopped = Stop();
     if (stopped) {
-      return true;
+      return StopperResult::kStopSuccess;
     }
     LOG(WARNING) << "Failed to stop VMM nicely, "
                   << "attempting to KILL";
-    return KillSubprocess(proc);
+    return KillSubprocess(proc) == StopperResult::kStopSuccess
+               ? StopperResult::kStopCrash
+               : StopperResult::kStopFailure;
   };
   std::string qemu_binary = config.qemu_binary_dir();
   switch (arch_) {
@@ -170,7 +172,7 @@
     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",
+        "virtio-serial-pci,max_ports=1,id=virtio-serial",
         hvc_num);
     qemu_cmd.AddParameter("-device");
     qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
@@ -207,7 +209,7 @@
                           ",append=on");
     qemu_cmd.AddParameter("-device");
     qemu_cmd.AddParameter(
-        "virtio-serial-pci-non-transitional,max_ports=1,id=virtio-serial",
+        "virtio-serial-pci,max_ports=1,id=virtio-serial",
         hvc_num);
     qemu_cmd.AddParameter("-device");
     qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
@@ -219,7 +221,7 @@
     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",
+        "virtio-serial-pci,max_ports=1,id=virtio-serial",
         hvc_num);
     qemu_cmd.AddParameter("-device");
     qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
@@ -374,7 +376,7 @@
     qemu_cmd.AddParameter("file=", disk, ",if=none,id=drive-virtio-disk", i,
                           ",aio=threads", format, readonly);
     qemu_cmd.AddParameter("-device");
-    qemu_cmd.AddParameter("virtio-blk-pci-non-transitional,scsi=off,drive=drive-virtio-disk", i,
+    qemu_cmd.AddParameter("virtio-blk-pci,scsi=off,drive=drive-virtio-disk", i,
                           ",id=virtio-disk", i, bootindex);
   }
 
@@ -417,36 +419,45 @@
   qemu_cmd.AddParameter("rng-random,id=objrng0,filename=/dev/urandom");
 
   qemu_cmd.AddParameter("-device");
-  qemu_cmd.AddParameter("virtio-rng-pci-non-transitional,rng=objrng0,id=rng0,",
+  qemu_cmd.AddParameter("virtio-rng-pci,rng=objrng0,id=rng0,",
                         "max-bytes=1024,period=2000");
 
   qemu_cmd.AddParameter("-device");
-  qemu_cmd.AddParameter("virtio-mouse-pci");
+  qemu_cmd.AddParameter("virtio-mouse-pci,disable-legacy=on");
 
   qemu_cmd.AddParameter("-device");
-  qemu_cmd.AddParameter("virtio-keyboard-pci");
+  qemu_cmd.AddParameter("virtio-keyboard-pci,disable-legacy=on");
+
+  // device padding for unsupported "switches" input
+  qemu_cmd.AddParameter("-device");
+  qemu_cmd.AddParameter("virtio-keyboard-pci,disable-legacy=on");
 
   auto vhost_net = config.vhost_net() ? ",vhost=on" : "";
 
   qemu_cmd.AddParameter("-device");
-  qemu_cmd.AddParameter("virtio-balloon-pci-non-transitional,id=balloon0");
+  qemu_cmd.AddParameter("virtio-balloon-pci,id=balloon0");
 
   qemu_cmd.AddParameter("-netdev");
   qemu_cmd.AddParameter("tap,id=hostnet0,ifname=", instance.wifi_tap_name(),
                         ",script=no,downscript=no", vhost_net);
 
   qemu_cmd.AddParameter("-device");
-  qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet0,id=net0");
+  qemu_cmd.AddParameter("virtio-net-pci,netdev=hostnet0,id=net0");
 
   qemu_cmd.AddParameter("-netdev");
   qemu_cmd.AddParameter("tap,id=hostnet1,ifname=", instance.mobile_tap_name(),
                         ",script=no,downscript=no", vhost_net);
 
   qemu_cmd.AddParameter("-device");
-  qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet1,id=net1");
+  qemu_cmd.AddParameter("virtio-net-pci,netdev=hostnet1,id=net1");
+
+  auto display_configs = config.display_configs();
+  CHECK_GE(display_configs.size(), 1);
+  auto display_config = display_configs[0];
 
   qemu_cmd.AddParameter("-device");
-  qemu_cmd.AddParameter("virtio-gpu-pci,id=gpu0");
+  qemu_cmd.AddParameter("virtio-gpu-pci,id=gpu0,"
+                        "xres=", display_config.width, ",yres=", display_config.height);
 
   qemu_cmd.AddParameter("-cpu");
   qemu_cmd.AddParameter(IsHostCompatible(arch_) ? "host" : "max");
@@ -455,7 +466,7 @@
   qemu_cmd.AddParameter("timestamp=on");
 
   qemu_cmd.AddParameter("-device");
-  qemu_cmd.AddParameter("vhost-vsock-pci-non-transitional,guest-cid=",
+  qemu_cmd.AddParameter("vhost-vsock-pci,guest-cid=",
                         instance.vsock_guest_cid());
 
   qemu_cmd.AddParameter("-device");
@@ -469,7 +480,7 @@
                           ",script=no,downscript=no", vhost_net);
 
     qemu_cmd.AddParameter("-device");
-    qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet2,id=net2");
+    qemu_cmd.AddParameter("virtio-net-pci,netdev=hostnet2,id=net2");
   }
 
   qemu_cmd.AddParameter("-device");
diff --git a/host/libs/wayland/Android.bp b/host/libs/wayland/Android.bp
index 4be6528..12700f3 100644
--- a/host/libs/wayland/Android.bp
+++ b/host/libs/wayland/Android.bp
@@ -28,6 +28,7 @@
         "wayland_subcompositor.cpp",
         "wayland_surface.cpp",
         "wayland_surfaces.cpp",
+        "wayland_virtio_gpu_metadata.cpp",
     ],
     shared_libs: [
         "libbase",
@@ -37,6 +38,7 @@
     static_libs: [
         "libdrm",
         "libffi",
+        "libwayland_crosvm_gpu_display_extension_server_protocols",
         "libwayland_server",
         "libwayland_extension_server_protocols",
     ],
diff --git a/host/libs/wayland/wayland_compositor.cpp b/host/libs/wayland/wayland_compositor.cpp
index 6179092..4e2f8f5 100644
--- a/host/libs/wayland/wayland_compositor.cpp
+++ b/host/libs/wayland/wayland_compositor.cpp
@@ -182,7 +182,10 @@
   .damage_buffer = surface_damage_buffer,
 };
 
-void surface_destroy_resource_callback(struct wl_resource*) {}
+void surface_destroy_resource_callback(struct wl_resource* surface_resource) {
+  Surface* surface = GetUserData<Surface>(surface_resource);
+  delete surface;
+}
 
 void compositor_create_surface(wl_client* client,
                                wl_resource* compositor,
@@ -191,12 +194,8 @@
                << " compositor=" << compositor
                << " id=" << id;
 
-  // Wayland seems to use a single global id space for all objects.
-  static std::atomic<std::uint32_t> sNextDisplayId{0};
-  uint32_t display_id = sNextDisplayId++;
-
   Surfaces* surfaces = GetUserData<Surfaces>(compositor);
-  Surface* surface = surfaces->GetOrCreateSurface(display_id);
+  Surface* surface = new Surface(*surfaces);
 
   wl_resource* surface_resource = wl_resource_create(
       client, &wl_surface_interface, wl_resource_get_version(compositor), id);
diff --git a/host/libs/wayland/wayland_server.cpp b/host/libs/wayland/wayland_server.cpp
index 4dc4b88..7dbc7f7 100644
--- a/host/libs/wayland/wayland_server.cpp
+++ b/host/libs/wayland/wayland_server.cpp
@@ -28,6 +28,7 @@
 #include "host/libs/wayland/wayland_subcompositor.h"
 #include "host/libs/wayland/wayland_surface.h"
 #include "host/libs/wayland/wayland_utils.h"
+#include "host/libs/wayland/wayland_virtio_gpu_metadata.h"
 
 namespace wayland {
 namespace internal {
@@ -79,6 +80,8 @@
   wl_display_init_shm(server_state_->display_);
 
   BindCompositorInterface(server_state_->display_, &server_state_->surfaces_);
+  BindVirtioGpuMetadataInterface(server_state_->display_,
+                                 &server_state_->surfaces_);
   BindDmabufInterface(server_state_->display_);
   BindSubcompositorInterface(server_state_->display_);
   BindSeatInterface(server_state_->display_);
@@ -94,8 +97,8 @@
   wl_display_destroy(server_state_->display_);
 }
 
-void WaylandServer::OnNextFrame(const Surfaces::FrameCallback& callback) {
-  server_state_->surfaces_.OnNextFrame(callback);
+void WaylandServer::SetFrameCallback(Surfaces::FrameCallback callback) {
+  server_state_->surfaces_.SetFrameCallback(std::move(callback));
 }
 
 }  // namespace wayland
diff --git a/host/libs/wayland/wayland_server.h b/host/libs/wayland/wayland_server.h
index 3149481..24ee68c 100644
--- a/host/libs/wayland/wayland_server.h
+++ b/host/libs/wayland/wayland_server.h
@@ -48,8 +48,9 @@
     WaylandServer(WaylandServer&& rhs) = delete;
     WaylandServer& operator=(WaylandServer&& rhs) = delete;
 
-    // Blocks until the given callback is run on the next frame available.
-    void OnNextFrame(const Surfaces::FrameCallback& callback);
+    // Registers the callback that will be run whenever a new frame is
+    // available.
+    void SetFrameCallback(Surfaces::FrameCallback callback);
 
    private:
     void ServerLoop(int wayland_socket_fd);
diff --git a/host/libs/wayland/wayland_shell.cpp b/host/libs/wayland/wayland_shell.cpp
index 439fc62..424c878 100644
--- a/host/libs/wayland/wayland_shell.cpp
+++ b/host/libs/wayland/wayland_shell.cpp
@@ -20,35 +20,29 @@
 
 #include <wayland-server-core.h>
 #include <wayland-server-protocol.h>
-#include <xdg-shell-unstable-v6-server-protocol.h>
+#include <xdg-shell-server-protocol.h>
 
 namespace wayland {
 namespace {
 
-
-void zxdg_positioner_v6_destroy(wl_client*, wl_resource* positioner) {
+void xdg_positioner_destroy(wl_client*, wl_resource* positioner) {
   LOG(VERBOSE) << __FUNCTION__
                << " positioner=" << positioner;
 
   wl_resource_destroy(positioner);
 }
 
-void zxdg_positioner_v6_set_size(wl_client*,
-                                 wl_resource* positioner,
-                                 int32_t w,
-                                 int32_t h) {
+void xdg_positioner_set_size(wl_client*, wl_resource* positioner, int32_t w,
+                             int32_t h) {
   LOG(VERBOSE) << __FUNCTION__
                << " positioner=" << positioner
                << " w=" << w
                << " h=" << h;
 }
 
-void zxdg_positioner_v6_set_anchor_rect(wl_client*,
-                                        wl_resource* positioner,
-                                        int32_t x,
-                                        int32_t y,
-                                        int32_t w,
-                                        int32_t h) {
+void xdg_positioner_set_anchor_rect(wl_client*, wl_resource* positioner,
+                                    int32_t x, int32_t y, int32_t w,
+                                    int32_t h) {
   LOG(VERBOSE) << __FUNCTION__
                << " positioner=" << positioner
                << " x=" << x
@@ -57,87 +51,76 @@
                << " h=" << h;
 }
 
-void zxdg_positioner_v6_set_anchor(wl_client*,
-                                   wl_resource* positioner,
-                                   uint32_t anchor) {
+void xdg_positioner_set_anchor(wl_client*, wl_resource* positioner,
+                               uint32_t anchor) {
   LOG(VERBOSE) << __FUNCTION__
                << " positioner=" << positioner
                << " anchor=" << anchor;
 }
 
-void zxdg_positioner_v6_set_gravity(wl_client*,
-                                    wl_resource* positioner,
-                                    uint32_t gravity) {
+void xdg_positioner_set_gravity(wl_client*, wl_resource* positioner,
+                                uint32_t gravity) {
   LOG(VERBOSE) << __FUNCTION__
                << " positioner=" << positioner
                << " gravity=" << gravity;
 }
 
-void zxdg_positioner_v6_set_constraint_adjustment(wl_client*,
-                                                  wl_resource* positioner,
-                                                  uint32_t adjustment) {
+void xdg_positioner_set_constraint_adjustment(wl_client*,
+                                              wl_resource* positioner,
+                                              uint32_t adjustment) {
   LOG(VERBOSE) << __FUNCTION__
                << " positioner=" << positioner
                << " adjustment=" << adjustment;
 }
 
-void zxdg_positioner_v6_set_offset(wl_client*,
-                                   wl_resource* positioner,
-                                   int32_t x,
-                                   int32_t y) {
+void xdg_positioner_set_offset(wl_client*, wl_resource* positioner, int32_t x,
+                               int32_t y) {
   LOG(VERBOSE) << __FUNCTION__
                << " positioner=" << positioner
                << " x=" << x
                << " y=" << y;
 }
 
-const struct zxdg_positioner_v6_interface
-    zxdg_positioner_v6_implementation = {
-        .destroy = zxdg_positioner_v6_destroy,
-        .set_size = zxdg_positioner_v6_set_size,
-        .set_anchor_rect = zxdg_positioner_v6_set_anchor_rect,
-        .set_anchor = zxdg_positioner_v6_set_anchor,
-        .set_gravity = zxdg_positioner_v6_set_gravity,
-        .set_constraint_adjustment = zxdg_positioner_v6_set_constraint_adjustment,
-        .set_offset = zxdg_positioner_v6_set_offset};
+const struct xdg_positioner_interface xdg_positioner_implementation = {
+    .destroy = xdg_positioner_destroy,
+    .set_size = xdg_positioner_set_size,
+    .set_anchor_rect = xdg_positioner_set_anchor_rect,
+    .set_anchor = xdg_positioner_set_anchor,
+    .set_gravity = xdg_positioner_set_gravity,
+    .set_constraint_adjustment = xdg_positioner_set_constraint_adjustment,
+    .set_offset = xdg_positioner_set_offset};
 
-void zxdg_toplevel_v6_destroy(wl_client*, wl_resource* toplevel) {
+void xdg_toplevel_destroy(wl_client*, wl_resource* toplevel) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel;
 
   wl_resource_destroy(toplevel);
 }
 
-void zxdg_toplevel_v6_set_parent(wl_client*,
-                                 wl_resource* toplevel,
-                                 wl_resource* parent_toplevel) {
+void xdg_toplevel_set_parent(wl_client*, wl_resource* toplevel,
+                             wl_resource* parent_toplevel) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel
                << " parent_toplevel=" << parent_toplevel;
 }
 
-void zxdg_toplevel_v6_set_title(wl_client*,
-                                wl_resource* toplevel,
-                                const char* title) {
+void xdg_toplevel_set_title(wl_client*, wl_resource* toplevel,
+                            const char* title) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel
                << " title=" << title;
 }
 
-void zxdg_toplevel_v6_set_app_id(wl_client*,
-                                 wl_resource* toplevel,
-                                 const char* app) {
+void xdg_toplevel_set_app_id(wl_client*, wl_resource* toplevel,
+                             const char* app) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel
                << " app=" << app;
 }
 
-void zxdg_toplevel_v6_show_window_menu(wl_client*,
-                                       wl_resource* toplevel,
-                                       wl_resource* seat,
-                                       uint32_t serial,
-                                       int32_t x,
-                                       int32_t y) {
+void xdg_toplevel_show_window_menu(wl_client*, wl_resource* toplevel,
+                                   wl_resource* seat, uint32_t serial,
+                                   int32_t x, int32_t y) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel
                << " seat=" << seat
@@ -146,21 +129,16 @@
                << " y=" << y;
 }
 
-void zxdg_toplevel_v6_move(wl_client*,
-                           wl_resource* toplevel,
-                           wl_resource* seat,
-                           uint32_t serial) {
+void xdg_toplevel_move(wl_client*, wl_resource* toplevel, wl_resource* seat,
+                       uint32_t serial) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel
                << " seat=" << seat
                << " serial=" << serial;
 }
 
-void zxdg_toplevel_v6_resize(wl_client*,
-                             wl_resource* toplevel,
-                             wl_resource* seat,
-                             uint32_t serial,
-                             uint32_t edges) {
+void xdg_toplevel_resize(wl_client*, wl_resource* toplevel, wl_resource* seat,
+                         uint32_t serial, uint32_t edges) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel
                << " seat=" << seat
@@ -168,93 +146,83 @@
                << " edges=" << edges;
 }
 
-void zxdg_toplevel_v6_set_max_size(wl_client*,
-                                   wl_resource* toplevel,
-                                   int32_t w,
-                                   int32_t h) {
+void xdg_toplevel_set_max_size(wl_client*, wl_resource* toplevel, int32_t w,
+                               int32_t h) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel
                << " w=" << w
                << " h=" << h;
 }
 
-void zxdg_toplevel_v6_set_min_size(wl_client*,
-                                   wl_resource* toplevel,
-                                   int32_t w,
-                                   int32_t h) {
+void xdg_toplevel_set_min_size(wl_client*, wl_resource* toplevel, int32_t w,
+                               int32_t h) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel
                << " w=" << w
                << " h=" << h;
 }
 
-void zxdg_toplevel_v6_set_maximized(wl_client*, wl_resource* toplevel) {
+void xdg_toplevel_set_maximized(wl_client*, wl_resource* toplevel) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel;
 }
 
-void zxdg_toplevel_v6_unset_maximized(wl_client*, wl_resource* toplevel) {
+void xdg_toplevel_unset_maximized(wl_client*, wl_resource* toplevel) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel;
 }
 
-void zxdg_toplevel_v6_set_fullscreen(wl_client*,
-                                     wl_resource* toplevel,
-                                     wl_resource*) {
+void xdg_toplevel_set_fullscreen(wl_client*, wl_resource* toplevel,
+                                 wl_resource*) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel;
 }
 
-void zxdg_toplevel_v6_unset_fullscreen(wl_client*, wl_resource* toplevel) {
+void xdg_toplevel_unset_fullscreen(wl_client*, wl_resource* toplevel) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel;
 }
 
-void zxdg_toplevel_v6_set_minimized(wl_client*, wl_resource* toplevel) {
+void xdg_toplevel_set_minimized(wl_client*, wl_resource* toplevel) {
   LOG(VERBOSE) << __FUNCTION__
                << " toplevel=" << toplevel;
 }
 
-const struct zxdg_toplevel_v6_interface zxdg_toplevel_v6_implementation = {
-  .destroy = zxdg_toplevel_v6_destroy,
-  .set_parent = zxdg_toplevel_v6_set_parent,
-  .set_title = zxdg_toplevel_v6_set_title,
-  .set_app_id = zxdg_toplevel_v6_set_app_id,
-  .show_window_menu = zxdg_toplevel_v6_show_window_menu,
-  .move = zxdg_toplevel_v6_move,
-  .resize = zxdg_toplevel_v6_resize,
-  .set_max_size = zxdg_toplevel_v6_set_max_size,
-  .set_min_size = zxdg_toplevel_v6_set_min_size,
-  .set_maximized = zxdg_toplevel_v6_set_maximized,
-  .unset_maximized = zxdg_toplevel_v6_unset_maximized,
-  .set_fullscreen = zxdg_toplevel_v6_set_fullscreen,
-  .unset_fullscreen = zxdg_toplevel_v6_unset_fullscreen,
-  .set_minimized = zxdg_toplevel_v6_set_minimized
-};
+const struct xdg_toplevel_interface xdg_toplevel_implementation = {
+    .destroy = xdg_toplevel_destroy,
+    .set_parent = xdg_toplevel_set_parent,
+    .set_title = xdg_toplevel_set_title,
+    .set_app_id = xdg_toplevel_set_app_id,
+    .show_window_menu = xdg_toplevel_show_window_menu,
+    .move = xdg_toplevel_move,
+    .resize = xdg_toplevel_resize,
+    .set_max_size = xdg_toplevel_set_max_size,
+    .set_min_size = xdg_toplevel_set_min_size,
+    .set_maximized = xdg_toplevel_set_maximized,
+    .unset_maximized = xdg_toplevel_unset_maximized,
+    .set_fullscreen = xdg_toplevel_set_fullscreen,
+    .unset_fullscreen = xdg_toplevel_unset_fullscreen,
+    .set_minimized = xdg_toplevel_set_minimized};
 
-void zxdg_popup_v6_destroy(wl_client*, wl_resource* popup) {
+void xdg_popup_destroy(wl_client*, wl_resource* popup) {
   LOG(VERBOSE) << __FUNCTION__
                << " popup=" << popup;
 
   wl_resource_destroy(popup);
 }
 
-void zxdg_popup_v6_grab(wl_client*,
-                        wl_resource* popup,
-                        wl_resource* seat,
-                        uint32_t serial) {
+void xdg_popup_grab(wl_client*, wl_resource* popup, wl_resource* seat,
+                    uint32_t serial) {
   LOG(VERBOSE) << __FUNCTION__
                << " popup=" << popup
                << " seat=" << seat
                << " serial=" << serial;
 }
 
-const struct zxdg_popup_v6_interface zxdg_popup_v6_implementation = {
-    .destroy = zxdg_popup_v6_destroy,
-    .grab = zxdg_popup_v6_grab
-};
+const struct xdg_popup_interface xdg_popup_implementation = {
+    .destroy = xdg_popup_destroy, .grab = xdg_popup_grab};
 
-void zxdg_surface_v6_destroy(wl_client*, wl_resource* surface) {
+void xdg_surface_destroy(wl_client*, wl_resource* surface) {
   LOG(VERBOSE) << __FUNCTION__
                << " surface=" << surface;
 
@@ -263,28 +231,25 @@
 
 void toplevel_destroy_resource_callback(struct wl_resource*) {}
 
-void zxdg_surface_v6_get_toplevel(wl_client* client,
-                                  wl_resource* surface,
-                                  uint32_t id) {
+void xdg_surface_get_toplevel(wl_client* client, wl_resource* surface,
+                              uint32_t id) {
   LOG(VERBOSE) << __FUNCTION__
                << " surface=" << surface
                << " id=" << id;
 
   wl_resource* xdg_toplevel_resource =
-      wl_resource_create(client, &zxdg_toplevel_v6_interface, 1, id);
+      wl_resource_create(client, &xdg_toplevel_interface, 1, id);
 
   wl_resource_set_implementation(xdg_toplevel_resource,
-                                 &zxdg_toplevel_v6_implementation, nullptr,
+                                 &xdg_toplevel_implementation, nullptr,
                                  toplevel_destroy_resource_callback);
 }
 
 void popup_destroy_resource_callback(struct wl_resource*) {}
 
-void zxdg_surface_v6_get_popup(wl_client* client,
-                              wl_resource* surface,
-                              uint32_t id,
-                              wl_resource* parent_surface,
-                              wl_resource* positioner) {
+void xdg_surface_get_popup(wl_client* client, wl_resource* surface, uint32_t id,
+                           wl_resource* parent_surface,
+                           wl_resource* positioner) {
   LOG(VERBOSE) << __FUNCTION__
                << " surface=" << surface
                << " id=" << id
@@ -292,19 +257,15 @@
                << " positioner=" << positioner;
 
   wl_resource* xdg_popup_resource =
-      wl_resource_create(client, &zxdg_popup_v6_interface, 1, id);
+      wl_resource_create(client, &xdg_popup_interface, 1, id);
 
-  wl_resource_set_implementation(xdg_popup_resource,
-                                 &zxdg_popup_v6_implementation, nullptr,
-                                 popup_destroy_resource_callback);
+  wl_resource_set_implementation(xdg_popup_resource, &xdg_popup_implementation,
+                                 nullptr, popup_destroy_resource_callback);
 }
 
-void zxdg_surface_v6_set_window_geometry(wl_client*,
-                                         wl_resource* surface,
-                                         int32_t x,
-                                         int32_t y,
-                                         int32_t w,
-                                         int32_t h) {
+void xdg_surface_set_window_geometry(wl_client*, wl_resource* surface,
+                                     int32_t x, int32_t y, int32_t w,
+                                     int32_t h) {
   LOG(VERBOSE) << __FUNCTION__
                << " surface=" << surface
                << " x=" << x
@@ -313,23 +274,21 @@
                << " h=" << h;
 }
 
-void zxdg_surface_v6_ack_configure(wl_client*,
-                                   wl_resource* surface,
-                                   uint32_t serial) {
+void xdg_surface_ack_configure(wl_client*, wl_resource* surface,
+                               uint32_t serial) {
   LOG(VERBOSE) << __FUNCTION__
                << " surface=" << surface
                << " serial=" << serial;
 }
 
-const struct zxdg_surface_v6_interface zxdg_surface_v6_implementation = {
-    .destroy = zxdg_surface_v6_destroy,
-    .get_toplevel = zxdg_surface_v6_get_toplevel,
-    .get_popup = zxdg_surface_v6_get_popup,
-    .set_window_geometry = zxdg_surface_v6_set_window_geometry,
-    .ack_configure = zxdg_surface_v6_ack_configure
-};
+const struct xdg_surface_interface xdg_surface_implementation = {
+    .destroy = xdg_surface_destroy,
+    .get_toplevel = xdg_surface_get_toplevel,
+    .get_popup = xdg_surface_get_popup,
+    .set_window_geometry = xdg_surface_set_window_geometry,
+    .ack_configure = xdg_surface_ack_configure};
 
-void zxdg_shell_v6_destroy(wl_client*, wl_resource* shell) {
+void xdg_shell_destroy(wl_client*, wl_resource* shell) {
   LOG(VERBOSE) << __FUNCTION__
                << " shell=" << shell;
 
@@ -338,65 +297,60 @@
 
 void positioner_destroy_resource_callback(struct wl_resource*) {}
 
-void zxdg_shell_v6_create_positioner(wl_client* client,
-                                     wl_resource* shell,
-                                     uint32_t id) {
+void xdg_shell_create_positioner(wl_client* client, wl_resource* shell,
+                                 uint32_t id) {
   LOG(VERBOSE) << __FUNCTION__
                << " shell=" << shell
                << " id=" << id;
 
   wl_resource* positioner_resource =
-      wl_resource_create(client, &zxdg_positioner_v6_interface, 1, id);
+      wl_resource_create(client, &xdg_positioner_interface, 1, id);
 
   wl_resource_set_implementation(positioner_resource,
-                                 &zxdg_positioner_v6_implementation, nullptr,
+                                 &xdg_positioner_implementation, nullptr,
                                  positioner_destroy_resource_callback);
 }
 
 void surface_destroy_resource_callback(struct wl_resource*) {}
 
-void zxdg_shell_v6_get_xdg_surface(wl_client* client,
-                                   wl_resource* shell,
-                                   uint32_t id,
-                                   wl_resource* surface) {
+void xdg_shell_get_xdg_surface(wl_client* client, wl_resource* shell,
+                               uint32_t id, wl_resource* surface) {
   LOG(VERBOSE) << __FUNCTION__
                << " shell=" << shell
                << " id=" << id
                << " surface=" << surface;
 
   wl_resource* surface_resource =
-      wl_resource_create(client, &zxdg_surface_v6_interface, 1, id);
+      wl_resource_create(client, &xdg_surface_interface, 1, id);
 
-  wl_resource_set_implementation(surface_resource,
-                                 &zxdg_surface_v6_implementation, nullptr,
-                                 surface_destroy_resource_callback);
+  wl_resource_set_implementation(surface_resource, &xdg_surface_implementation,
+                                 nullptr, surface_destroy_resource_callback);
 }
 
-void zxdg_shell_v6_pong(wl_client*, wl_resource* shell, uint32_t serial) {
+void xdg_shell_pong(wl_client*, wl_resource* shell, uint32_t serial) {
   LOG(VERBOSE) << __FUNCTION__
                << " shell=" << shell
                << " serial=" << serial;
 }
 
-const struct zxdg_shell_v6_interface zxdg_shell_v6_implementation = {
-    .destroy = zxdg_shell_v6_destroy,
-    .create_positioner = zxdg_shell_v6_create_positioner,
-    .get_xdg_surface = zxdg_shell_v6_get_xdg_surface,
-    .pong = zxdg_shell_v6_pong
-};
+const struct xdg_wm_base_interface xdg_shell_implementation = {
+    .destroy = xdg_shell_destroy,
+    .create_positioner = xdg_shell_create_positioner,
+    .get_xdg_surface = xdg_shell_get_xdg_surface,
+    .pong = xdg_shell_pong};
 
 void bind_shell(wl_client* client, void* data, uint32_t version, uint32_t id) {
   wl_resource* shell_resource =
-      wl_resource_create(client, &zxdg_shell_v6_interface, version, id);
+      wl_resource_create(client, &xdg_wm_base_interface, version, id);
 
-  wl_resource_set_implementation(shell_resource,
-                                 &zxdg_shell_v6_implementation, data, nullptr);
+  wl_resource_set_implementation(shell_resource, &xdg_shell_implementation,
+                                 data, nullptr);
 }
 
 }  // namespace
 
 void BindShellInterface(wl_display* display) {
-  wl_global_create(display, &zxdg_shell_v6_interface, 1, nullptr, bind_shell);
+  wl_global_create(display, &xdg_wm_base_interface, 1, nullptr, bind_shell);
 }
 
 }  // namespace wayland
\ No newline at end of file
diff --git a/host/libs/wayland/wayland_surface.cpp b/host/libs/wayland/wayland_surface.cpp
index e9abece..51a3b42 100644
--- a/host/libs/wayland/wayland_surface.cpp
+++ b/host/libs/wayland/wayland_surface.cpp
@@ -23,8 +23,7 @@
 
 namespace wayland {
 
-Surface::Surface(std::uint32_t display_number, Surfaces& surfaces)
-    : display_number_(display_number), surfaces_(surfaces) {}
+Surface::Surface(Surfaces& surfaces) : surfaces_(surfaces) {}
 
 void Surface::SetRegion(const Region& region) {
   std::unique_lock<std::mutex> lock(state_mutex_);
@@ -45,22 +44,28 @@
     return;
   }
 
-  struct wl_shm_buffer* shm_buffer = wl_shm_buffer_get(state_.current_buffer);
-  CHECK(shm_buffer != nullptr);
+  if (state_.virtio_gpu_metadata_.scanout_id.has_value()) {
+    const uint32_t display_number = *state_.virtio_gpu_metadata_.scanout_id;
 
-  wl_shm_buffer_begin_access(shm_buffer);
+    struct wl_shm_buffer* shm_buffer = wl_shm_buffer_get(state_.current_buffer);
+    CHECK(shm_buffer != nullptr);
 
-  const int32_t buffer_w = wl_shm_buffer_get_width(shm_buffer);
-  CHECK(buffer_w == state_.region.w);
-  const int32_t buffer_h = wl_shm_buffer_get_height(shm_buffer);
-  CHECK(buffer_h == state_.region.h);
+    wl_shm_buffer_begin_access(shm_buffer);
 
-  uint8_t* buffer_pixels =
-      reinterpret_cast<uint8_t*>(wl_shm_buffer_get_data(shm_buffer));
+    const int32_t buffer_w = wl_shm_buffer_get_width(shm_buffer);
+    CHECK(buffer_w == state_.region.w);
+    const int32_t buffer_h = wl_shm_buffer_get_height(shm_buffer);
+    CHECK(buffer_h == state_.region.h);
+    const int32_t buffer_stride_bytes = wl_shm_buffer_get_stride(shm_buffer);
 
-  surfaces_.HandleSurfaceFrame(display_number_, buffer_pixels);
+    uint8_t* buffer_pixels =
+        reinterpret_cast<uint8_t*>(wl_shm_buffer_get_data(shm_buffer));
 
-  wl_shm_buffer_end_access(shm_buffer);
+    surfaces_.HandleSurfaceFrame(display_number, buffer_w, buffer_h,
+                                 buffer_stride_bytes, buffer_pixels);
+
+    wl_shm_buffer_end_access(shm_buffer);
+  }
 
   wl_buffer_send_release(state_.current_buffer);
   wl_client_flush(wl_resource_get_client(state_.current_buffer));
@@ -69,4 +74,9 @@
   state_.current_frame_number++;
 }
 
+void Surface::SetVirtioGpuScanoutId(uint32_t scanout_id) {
+  std::unique_lock<std::mutex> lock(state_mutex_);
+  state_.virtio_gpu_metadata_.scanout_id = scanout_id;
+}
+
 }  // namespace wayland
\ No newline at end of file
diff --git a/host/libs/wayland/wayland_surface.h b/host/libs/wayland/wayland_surface.h
index fcf2345..f3b17ae 100644
--- a/host/libs/wayland/wayland_surface.h
+++ b/host/libs/wayland/wayland_surface.h
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 #include <mutex>
+#include <optional>
 
 #include <wayland-server-core.h>
 
@@ -29,7 +30,7 @@
 // Tracks the buffer associated with a Wayland surface.
 class Surface {
  public:
-  Surface(std::uint32_t display_number, Surfaces& surfaces);
+  Surface(Surfaces& surfaces);
   virtual ~Surface() = default;
 
   Surface(const Surface& rhs) = delete;
@@ -53,10 +54,15 @@
   // Commits the pending frame state.
   void Commit();
 
+  void SetVirtioGpuScanoutId(uint32_t scanout);
+
  private:
-  std::uint32_t display_number_;
   Surfaces& surfaces_;
 
+  struct VirtioGpuMetadata {
+    std::optional<uint32_t> scanout_id;
+  };
+
   struct State {
     uint32_t current_frame_number = 0;
 
@@ -68,6 +74,8 @@
 
     // The buffers expected dimensions.
     Region region;
+
+    VirtioGpuMetadata virtio_gpu_metadata_;
   };
 
   std::mutex state_mutex_;
diff --git a/host/libs/wayland/wayland_surfaces.cpp b/host/libs/wayland/wayland_surfaces.cpp
index 7096574..2fa1ed9 100644
--- a/host/libs/wayland/wayland_surfaces.cpp
+++ b/host/libs/wayland/wayland_surfaces.cpp
@@ -23,42 +23,20 @@
 
 namespace wayland {
 
-Surface* Surfaces::GetOrCreateSurface(std::uint32_t id) {
-  std::unique_lock<std::mutex> lock(surfaces_mutex_);
-
-  auto [it, inserted] = surfaces_.try_emplace(id, nullptr);
-
-  std::unique_ptr<Surface>& surface_ptr = it->second;
-  if (inserted) {
-    surface_ptr.reset(new Surface(id, *this));
-  }
-  return surface_ptr.get();
-}
-
-void Surfaces::OnNextFrame(const FrameCallback& frame_callback) {
-  // Wraps the given callback in a std::package_task that can be waited upon
-  // for completion.
-  Surfaces::FrameCallbackPackaged frame_callback_packaged(
-      [&frame_callback](std::uint32_t display_number,
-                        std::uint8_t* frame_pixels) {
-        frame_callback(display_number, frame_pixels);
-      });
-
-  {
-    std::unique_lock<std::mutex> lock(callback_mutex_);
-    callback_.emplace(&frame_callback_packaged);
-  }
-
-  // Blocks until the frame_callback_packaged was called.
-  frame_callback_packaged.get_future().get();
+void Surfaces::SetFrameCallback(FrameCallback callback) {
+  std::unique_lock<std::mutex> lock(callback_mutex_);
+  callback_.emplace(std::move(callback));
 }
 
 void Surfaces::HandleSurfaceFrame(std::uint32_t display_number,
+                                  std::uint32_t frame_width,
+                                  std::uint32_t frame_height,
+                                  std::uint32_t frame_stride_bytes,
                                   std::uint8_t* frame_bytes) {
   std::unique_lock<std::mutex> lock(callback_mutex_);
   if (callback_) {
-    (*callback_.value())(display_number, frame_bytes);
-    callback_.reset();
+    (callback_.value())(display_number, frame_width, frame_height,
+                        frame_stride_bytes, frame_bytes);
   }
 }
 
diff --git a/host/libs/wayland/wayland_surfaces.h b/host/libs/wayland/wayland_surfaces.h
index ec67ca4..58ed0e8 100644
--- a/host/libs/wayland/wayland_surfaces.h
+++ b/host/libs/wayland/wayland_surfaces.h
@@ -39,27 +39,25 @@
   Surfaces(Surfaces&& rhs) = delete;
   Surfaces& operator=(Surfaces&& rhs) = delete;
 
-  Surface* GetOrCreateSurface(std::uint32_t id);
+  using FrameCallback =
+      std::function<void(std::uint32_t /*display_number*/,      //
+                         std::uint32_t /*frame_width*/,         //
+                         std::uint32_t /*frame_height*/,        //
+                         std::uint32_t /*frame_stride_bytes*/,  //
+                         std::uint8_t* /*frame_bytes*/)>;
 
-  using FrameCallback = std::function<void(std::uint32_t /*display_number*/,
-                                           std::uint8_t* /*frame_pixels*/)>;
-
-  // Blocking
-  void OnNextFrame(const FrameCallback& callback);
+  void SetFrameCallback(FrameCallback callback);
 
  private:
   friend class Surface;
-  void HandleSurfaceFrame(std::uint32_t display_number,
+  void HandleSurfaceFrame(std::uint32_t display_number,      //
+                          std::uint32_t frame_width,         //
+                          std::uint32_t frame_height,        //
+                          std::uint32_t frame_stride_bytes,  //
                           std::uint8_t* frame_bytes);
 
-  std::mutex surfaces_mutex_;
-  std::unordered_map<std::uint32_t, std::unique_ptr<Surface>> surfaces_;
-
-  using FrameCallbackPackaged = std::packaged_task<void(
-      std::uint32_t /*display_number*/, std::uint8_t* /*frame_bytes*/)>;
-
   std::mutex callback_mutex_;
-  std::optional<FrameCallbackPackaged*> callback_;
+  std::optional<FrameCallback> callback_;
 };
 
 }  // namespace wayland
diff --git a/host/libs/wayland/wayland_virtio_gpu_metadata.cpp b/host/libs/wayland/wayland_virtio_gpu_metadata.cpp
new file mode 100644
index 0000000..e26c653
--- /dev/null
+++ b/host/libs/wayland/wayland_virtio_gpu_metadata.cpp
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#include "host/libs/wayland/wayland_compositor.h"
+
+#include <android-base/logging.h>
+
+#include <virtio-gpu-metadata-v1.h>
+#include <wayland-server-core.h>
+#include <wayland-server-protocol.h>
+
+#include "host/libs/wayland/wayland_surface.h"
+#include "host/libs/wayland/wayland_utils.h"
+
+namespace wayland {
+namespace {
+
+void virtio_gpu_surface_metadata_set_scanout_id(
+    struct wl_client*, struct wl_resource* surface_metadata_resource,
+    uint32_t scanout_id) {
+  GetUserData<Surface>(surface_metadata_resource)
+      ->SetVirtioGpuScanoutId(scanout_id);
+}
+
+const struct wp_virtio_gpu_surface_metadata_v1_interface
+    virtio_gpu_surface_metadata_implementation = {
+        .set_scanout_id = virtio_gpu_surface_metadata_set_scanout_id};
+
+void destroy_virtio_gpu_surface_metadata_resource_callback(
+    struct wl_resource*) {
+  // This is only expected to occur upon surface destruction so no need to
+  // update the scanout id in `Surface`.
+}
+
+void virtio_gpu_metadata_get_surface_metadata(
+    struct wl_client* client, struct wl_resource* /*metadata_impl_resource*/,
+    uint32_t id, struct wl_resource* surface_resource) {
+  Surface* surface = GetUserData<Surface>(surface_resource);
+
+  wl_resource* virtio_gpu_metadata_surface_resource = wl_resource_create(
+      client, &wp_virtio_gpu_surface_metadata_v1_interface, 1, id);
+
+  wl_resource_set_implementation(
+      virtio_gpu_metadata_surface_resource,
+      &virtio_gpu_surface_metadata_implementation, surface,
+      destroy_virtio_gpu_surface_metadata_resource_callback);
+}
+
+const struct wp_virtio_gpu_metadata_v1_interface
+    virtio_gpu_metadata_implementation = {
+        .get_surface_metadata = virtio_gpu_metadata_get_surface_metadata,
+};
+
+void destroy_virtio_gpu_metadata_resource_callback(struct wl_resource*) {}
+
+void bind_virtio_gpu_metadata(wl_client* client, void* data,
+                              uint32_t /*version*/, uint32_t id) {
+  wl_resource* resource =
+      wl_resource_create(client, &wp_virtio_gpu_metadata_v1_interface, 1, id);
+
+  wl_resource_set_implementation(resource, &virtio_gpu_metadata_implementation,
+                                 data,
+                                 destroy_virtio_gpu_metadata_resource_callback);
+}
+
+}  // namespace
+
+void BindVirtioGpuMetadataInterface(wl_display* display, Surfaces* surfaces) {
+  wl_global_create(display, &wp_virtio_gpu_metadata_v1_interface, 1, surfaces,
+                   bind_virtio_gpu_metadata);
+}
+
+}  // namespace wayland
\ No newline at end of file
diff --git a/common/libs/utils/size_utils.cpp b/host/libs/wayland/wayland_virtio_gpu_metadata.h
similarity index 60%
copy from common/libs/utils/size_utils.cpp
copy to host/libs/wayland/wayland_virtio_gpu_metadata.h
index 9f25445..c414c84 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/libs/wayland/wayland_virtio_gpu_metadata.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-#include "common/libs/utils/size_utils.h"
+#pragma once
 
-#include <unistd.h>
+#include <stdint.h>
 
-namespace cuttlefish {
+#include <wayland-server-core.h>
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+#include "host/libs/wayland/wayland_surfaces.h"
 
-}  // namespace cuttlefish
+namespace wayland {
+
+// Binds the virtio gpu metadata interface to the given wayland server.
+void BindVirtioGpuMetadataInterface(wl_display* display, Surfaces* surfaces);
+
+}  // namespace wayland
\ No newline at end of file
diff --git a/host/libs/websocket/Android.bp b/host/libs/websocket/Android.bp
index 2a4a522..327224b 100644
--- a/host/libs/websocket/Android.bp
+++ b/host/libs/websocket/Android.bp
@@ -28,6 +28,7 @@
         "liblog",
         "libssl",
         "libcrypto",
+        "libcuttlefish_utils",
     ],
     static_libs: [
         "libcap",
diff --git a/host/libs/websocket/websocket_handler.cpp b/host/libs/websocket/websocket_handler.cpp
index 59b1e1e..4d93ebd 100644
--- a/host/libs/websocket/websocket_handler.cpp
+++ b/host/libs/websocket/websocket_handler.cpp
@@ -18,9 +18,17 @@
 #include <android-base/logging.h>
 #include <libwebsockets.h>
 
+#include "host/libs/websocket/websocket_server.h"
+
 namespace cuttlefish {
 
-const size_t WebSocketHandler::WsBuffer::kLwsPre = LWS_PRE;
+namespace {
+void AppendData(const char* data, size_t len, std::string& buffer) {
+  auto ptr = reinterpret_cast<const uint8_t*>(data);
+  buffer.reserve(buffer.size() + len);
+  buffer.insert(buffer.end(), ptr, ptr + len);
+}
+}  // namespace
 
 WebSocketHandler::WebSocketHandler(struct lws* wsi) : wsi_(wsi) {}
 
@@ -34,32 +42,29 @@
 
 // Attempts to write what's left on a websocket buffer to the websocket,
 // updating the buffer.
-// Returns true if the entire buffer was successfully written.
-bool WebSocketHandler::WriteWsBuffer(WebSocketHandler::WsBuffer& ws_buffer) {
-  auto len = ws_buffer.data.size() - ws_buffer.start;
+void WebSocketHandler::WriteWsBuffer(WebSocketHandler::WsBuffer& ws_buffer) {
+  auto len = ws_buffer.data.size() - LWS_PRE;
   auto flags = lws_write_ws_flags(
       ws_buffer.binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT, true, true);
-  auto res = lws_write(wsi_, &ws_buffer.data[ws_buffer.start], len,
+  auto res = lws_write(wsi_, &ws_buffer.data[LWS_PRE], len,
                        static_cast<enum lws_write_protocol>(flags));
+  // lws_write will write all bytes of the provided buffer or enqueue the ones
+  // it couldn't write for later, but it guarantees it will consume the entire
+  // buffer, so we only need to check for error.
   if (res < 0) {
     // This shouldn't happen since this function is called in response to a
     // LWS_CALLBACK_SERVER_WRITEABLE call.
-    LOG(FATAL) << "Failed to write data on the websocket";
-    // Close
-    return true;
+    LOG(ERROR) << "Failed to write data on the websocket";
   }
-  ws_buffer.start += res;
-  return ws_buffer.start == ws_buffer.data.size();
 }
 
 bool WebSocketHandler::OnWritable() {
   if (buffer_queue_.empty()) {
     return close_;
   }
-  auto wrote_full_buffer = WriteWsBuffer(buffer_queue_.back());
-  if (wrote_full_buffer) {
-    buffer_queue_.pop_back();
-  }
+  WriteWsBuffer(buffer_queue_.back());
+  buffer_queue_.pop_back();
+
   if (!buffer_queue_.empty()) {
     lws_callback_on_writable(wsi_);
   }
@@ -72,4 +77,24 @@
   lws_callback_on_writable(wsi_);
 }
 
+DynHandler::DynHandler(struct lws* wsi) : wsi_(wsi), out_buffer_(LWS_PRE, 0) {}
+
+void DynHandler::AppendDataOut(const std::string& data) {
+  AppendData(data.c_str(), data.size(), out_buffer_);
+}
+
+void DynHandler::AppendDataIn(void* data, size_t len) {
+  AppendData(reinterpret_cast<char*>(data), len, in_buffer_);
+}
+
+void DynHandler::OnWritable() {
+  auto len = out_buffer_.size() - LWS_PRE;
+  auto res = lws_write(wsi_, reinterpret_cast<uint8_t*>(&out_buffer_[LWS_PRE]),
+                       len, LWS_WRITE_HTTP_FINAL);
+  if (res != len) {
+    // This shouldn't happen since this function is called in response to a
+    // LWS_CALLBACK_SERVER_WRITEABLE call.
+    LOG(ERROR) << "Failed to write HTTP response";
+  }
+}
 }  // namespace cuttlefish
diff --git a/host/libs/websocket/websocket_handler.h b/host/libs/websocket/websocket_handler.h
index 0111b1e..572ef76 100644
--- a/host/libs/websocket/websocket_handler.h
+++ b/host/libs/websocket/websocket_handler.h
@@ -16,6 +16,7 @@
 #pragma once
 
 #include <deque>
+#include <string>
 #include <vector>
 
 struct lws;
@@ -28,6 +29,10 @@
   virtual ~WebSocketHandler() = default;
 
   virtual void OnReceive(const uint8_t* msg, size_t len, bool binary) = 0;
+  virtual void OnReceive(const uint8_t* msg, size_t len, bool binary,
+                         [[maybe_unused]] bool is_final) {
+    OnReceive(msg, len, binary);
+  }
   virtual void OnConnected() = 0;
   virtual void OnClosed() = 0;
 
@@ -40,15 +45,13 @@
 
  private:
   struct WsBuffer {
-    static const size_t kLwsPre;
     WsBuffer(std::vector<uint8_t> data, bool binary)
         : data(std::move(data)), binary(binary) {}
     std::vector<uint8_t> data;
     bool binary;
-    size_t start = kLwsPre;
   };
 
-  bool WriteWsBuffer(WsBuffer& ws_buffer);
+  void WriteWsBuffer(WsBuffer& ws_buffer);
 
   struct lws* wsi_;
   bool close_ = false;
@@ -61,4 +64,39 @@
   virtual std::shared_ptr<WebSocketHandler> Build(struct lws* wsi) = 0;
 };
 
+class WebSocketServer;
+
+class DynHandler {
+ public:
+  DynHandler(struct lws* wsi);
+
+  virtual ~DynHandler() = default;
+  // TODO (jemoreira): Allow more than just JSON replies
+  // TODO (jemoreira): Receive request parameters
+  // Handle a GET request. Returns the status code of the request.
+  virtual int DoGet() = 0;
+  // Handle a POST request. Returns the status code of the request.
+  virtual int DoPost() = 0;
+
+ protected:
+  void AppendDataOut(const std::string& data);
+  const std::string& GetDataIn() const { return in_buffer_; }
+
+ private:
+  friend WebSocketServer;
+  void AppendDataIn(void* data, size_t len);
+  void OnWritable();
+
+  struct lws* wsi_;
+  std::string in_buffer_ = {};
+  std::string out_buffer_ = {};
+};
+
+class DynHandlerFactory {
+ public:
+  virtual ~DynHandlerFactory() = default;
+  // A new Handler will be created for each connection
+  virtual std::unique_ptr<DynHandler> Build(struct lws* wsi) = 0;
+};
+
 }  // namespace cuttlefish
diff --git a/host/libs/websocket/websocket_server.cpp b/host/libs/websocket/websocket_server.cpp
index 75110f8..2b87ab6 100644
--- a/host/libs/websocket/websocket_server.cpp
+++ b/host/libs/websocket/websocket_server.cpp
@@ -22,31 +22,136 @@
 #include <android-base/logging.h>
 #include <libwebsockets.h>
 
+#include <common/libs/utils/files.h>
 #include <host/libs/websocket/websocket_handler.h>
 
 namespace cuttlefish {
-WebSocketServer::WebSocketServer(
-    const char* protocol_name,
-    const std::string &certs_dir,
-    const std::string &assets_dir,
-    int server_port) {
-  std::string cert_file = certs_dir + "/server.crt";
-  std::string key_file = certs_dir + "/server.key";
+namespace {
+
+std::string GetPath(struct lws* wsi) {
+  auto len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI);
+  std::string path(len + 1, '\0');
+  auto ret = lws_hdr_copy(wsi, path.data(), path.size(), WSI_TOKEN_GET_URI);
+  if (ret <= 0) {
+    len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH);
+    path.resize(len + 1, '\0');
+    ret =
+        lws_hdr_copy(wsi, path.data(), path.size(), WSI_TOKEN_HTTP_COLON_PATH);
+  }
+  if (ret < 0) {
+    LOG(FATAL) << "Something went wrong getting the path";
+  }
+  path.resize(len);
+  return path;
+}
+
+bool WriteCommonHttpHeaders(int status, const char* mime_type,
+                            struct lws* wsi) {
+  constexpr size_t BUFF_SIZE = 2048;
+  uint8_t header_buffer[LWS_PRE + BUFF_SIZE];
+  const auto start = &header_buffer[LWS_PRE];
+  auto p = &header_buffer[LWS_PRE];
+  auto end = start + BUFF_SIZE;
+  if (lws_add_http_common_headers(wsi, status, mime_type,
+                                  LWS_ILLEGAL_HTTP_CONTENT_LEN, &p, end)) {
+    LOG(ERROR) << "Failed to write headers for response";
+    return false;
+  }
+  if (lws_finalize_write_http_header(wsi, start, &p, end)) {
+    LOG(ERROR) << "Failed to finalize headers for response";
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+WebSocketServer::WebSocketServer(const char* protocol_name,
+                                 const std::string& assets_dir, int server_port)
+    : WebSocketServer(protocol_name, "", assets_dir, server_port) {}
+
+WebSocketServer::WebSocketServer(const char* protocol_name,
+                                 const std::string& certs_dir,
+                                 const std::string& assets_dir, int server_port)
+    : protocol_name_(protocol_name),
+      assets_dir_(assets_dir),
+      certs_dir_(certs_dir),
+      server_port_(server_port) {}
+
+void WebSocketServer::InitializeLwsObjects() {
+  std::string cert_file = certs_dir_ + "/server.crt";
+  std::string key_file = certs_dir_ + "/server.key";
+  std::string ca_file = certs_dir_ + "/CA.crt";
 
   retry_ = {
       .secs_since_valid_ping = 3,
       .secs_since_valid_hangup = 10,
   };
 
-  struct lws_protocols protocols[] = {
-      {protocol_name, ServerCallback, 4096, 0, 0, nullptr, 0},
-      {nullptr, nullptr, 0, 0, 0, nullptr, 0}};
+  struct lws_protocols protocols[] =  //
+      {{
+           .name = protocol_name_.c_str(),
+           .callback = WebsocketCallback,
+           .per_session_data_size = 0,
+           .rx_buffer_size = 0,
+           .id = 0,
+           .user = this,
+           .tx_packet_size = 0,
+       },
+       {
+           .name = "__http_polling__",
+           .callback = DynHttpCallback,
+           .per_session_data_size = 0,
+           .rx_buffer_size = 0,
+           .id = 0,
+           .user = this,
+           .tx_packet_size = 0,
+       },
+       {
+           .name = nullptr,
+           .callback = nullptr,
+           .per_session_data_size = 0,
+           .rx_buffer_size = 0,
+           .id = 0,
+           .user = nullptr,
+           .tx_packet_size = 0,
+       }};
 
-  mount_ = {
-      .mount_next = nullptr,
+  dyn_mounts_.reserve(dyn_handler_factories_.size());
+  for (auto& handler_entry : dyn_handler_factories_) {
+    auto& path = handler_entry.first;
+    dyn_mounts_.push_back({
+        .mount_next = nullptr,
+        .mountpoint = path.c_str(),
+        .mountpoint_len = static_cast<uint8_t>(path.size()),
+        .origin = "__http_polling__",
+        .def = nullptr,
+        .protocol = nullptr,
+        .cgienv = nullptr,
+        .extra_mimetypes = nullptr,
+        .interpret = nullptr,
+        .cgi_timeout = 0,
+        .cache_max_age = 0,
+        .auth_mask = 0,
+        .cache_reusable = 0,
+        .cache_revalidate = 0,
+        .cache_intermediaries = 0,
+        .origin_protocol = LWSMPRO_CALLBACK,  // dynamic
+        .basic_auth_login_file = nullptr,
+    });
+  }
+  struct lws_http_mount* next_mount = nullptr;
+  // Set up the linked list after all the mounts have been created to ensure
+  // pointers are not invalidated.
+  for (auto& mount : dyn_mounts_) {
+    mount.mount_next = next_mount;
+    next_mount = &mount;
+  }
+
+  static_mount_ = {
+      .mount_next = next_mount,
       .mountpoint = "/",
       .mountpoint_len = 1,
-      .origin = assets_dir.c_str(),
+      .origin = assets_dir_.c_str(),
       .def = "index.html",
       .protocol = nullptr,
       .cgienv = nullptr,
@@ -63,24 +168,29 @@
   };
 
   struct lws_context_creation_info info;
-  headers_ = {NULL, NULL,
-    "content-security-policy:",
-      "default-src 'self'; "
-      "style-src 'self' https://fonts.googleapis.com/; "
-      "font-src  https://fonts.gstatic.com/; "};
+  headers_ = {NULL, NULL, "content-security-policy:",
+              "default-src 'self' https://ajax.googleapis.com; "
+              "style-src 'self' https://fonts.googleapis.com/; "
+              "font-src  https://fonts.gstatic.com/; "};
 
   memset(&info, 0, sizeof info);
-  info.port = server_port;
-  info.mounts = &mount_;
+  info.port = server_port_;
+  info.mounts = &static_mount_;
   info.protocols = protocols;
   info.vhost_name = "localhost";
   info.ws_ping_pong_interval = 10;
   info.headers = &headers_;
-  info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-  info.ssl_cert_filepath = cert_file.c_str();
-  info.ssl_private_key_filepath = key_file.c_str();
   info.retry_and_idle_policy = &retry_;
 
+  if (!certs_dir_.empty()) {
+    info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+    info.ssl_cert_filepath = cert_file.c_str();
+    info.ssl_private_key_filepath = key_file.c_str();
+    if (FileExists(ca_file)) {
+      info.ssl_ca_filepath = ca_file.c_str();
+    }
+  }
+
   context_ = lws_create_context(&info);
   if (!context_) {
     LOG(FATAL) << "Failed to create websocket context";
@@ -88,12 +198,19 @@
 }
 
 void WebSocketServer::RegisterHandlerFactory(
-    const std::string &path,
+    const std::string& path,
     std::unique_ptr<WebSocketHandlerFactory> handler_factory_p) {
   handler_factories_[path] = std::move(handler_factory_p);
 }
 
+void WebSocketServer::RegisterDynHandlerFactory(
+    const std::string& path,
+    std::unique_ptr<DynHandlerFactory> handler_factory_p) {
+  dyn_handler_factories_[path] = std::move(handler_factory_p);
+}
+
 void WebSocketServer::Serve() {
+  InitializeLwsObjects();
   int n = 0;
   while (n >= 0) {
     n = lws_service(context_, 0);
@@ -101,22 +218,104 @@
   lws_context_destroy(context_);
 }
 
-std::unordered_map<struct lws*, std::shared_ptr<WebSocketHandler>> WebSocketServer::handlers_ = {};
-std::unordered_map<std::string, std::unique_ptr<WebSocketHandlerFactory>>
-    WebSocketServer::handler_factories_ = {};
-
-std::string WebSocketServer::GetPath(struct lws* wsi) {
-  auto len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI);
-  std::string path(len + 1, '\0');
-  auto ret = lws_hdr_copy(wsi, path.data(), path.size(), WSI_TOKEN_GET_URI);
-  if (ret < 0) {
-    LOG(FATAL) << "Something went wrong getting the path";
+int WebSocketServer::WebsocketCallback(struct lws* wsi,
+                                       enum lws_callback_reasons reason,
+                                       void* user, void* in, size_t len) {
+  auto protocol = lws_get_protocol(wsi);
+  if (!protocol) {
+    // Some callback reasons are always handled by the first protocol, before a
+    // wsi struct is even created.
+    return lws_callback_http_dummy(wsi, reason, user, in, len);
   }
-  path.resize(len);
-  return path;
+  return reinterpret_cast<WebSocketServer*>(protocol->user)
+      ->ServerCallback(wsi, reason, user, in, len);
 }
 
-int WebSocketServer::ServerCallback(struct lws* wsi, enum lws_callback_reasons reason,
+int WebSocketServer::DynHttpCallback(struct lws* wsi,
+                                     enum lws_callback_reasons reason,
+                                     void* user, void* in, size_t len) {
+  auto protocol = lws_get_protocol(wsi);
+  if (!protocol) {
+    LOG(ERROR) << "No protocol associated with connection";
+    return 1;
+  }
+  return reinterpret_cast<WebSocketServer*>(protocol->user)
+      ->DynServerCallback(wsi, reason, user, in, len);
+}
+
+int WebSocketServer::DynServerCallback(struct lws* wsi,
+                                       enum lws_callback_reasons reason,
+                                       void* user, void* in, size_t len) {
+  switch (reason) {
+    case LWS_CALLBACK_HTTP: {
+      char* path_raw;
+      int path_len;
+      auto method = lws_http_get_uri_and_method(wsi, &path_raw, &path_len);
+      if (method < 0) {
+        return 1;
+      }
+      std::string path(path_raw, path_len);
+      auto handler = InstantiateDynHandler(path, wsi);
+      dyn_handlers_[wsi] = std::move(handler);
+      switch (method) {
+        case LWSHUMETH_GET: {
+          auto status = dyn_handlers_[wsi]->DoGet();
+          if (!WriteCommonHttpHeaders(status, "application/json", wsi)) {
+            return 1;
+          }
+          // Write the response later, when the server is ready
+          lws_callback_on_writable(wsi);
+          break;
+        }
+        case LWSHUMETH_POST:
+          // Do nothing until the body has been read
+          break;
+        default:
+          LOG(ERROR) << "Unsupported HTTP method: " << method;
+          return 1;
+      }
+      break;
+    }
+    case LWS_CALLBACK_HTTP_BODY: {
+      auto handler = dyn_handlers_[wsi].get();
+      if (!handler) {
+        LOG(WARNING) << "Received body for unknown wsi";
+        return 1;
+      }
+      handler->AppendDataIn(in, len);
+      break;
+    }
+    case LWS_CALLBACK_HTTP_BODY_COMPLETION: {
+      auto handler = dyn_handlers_[wsi].get();
+      auto status = handler->DoPost();
+      if (!WriteCommonHttpHeaders(status, "application/json", wsi)) {
+        return 1;
+      }
+      lws_callback_on_writable(wsi);
+      break;
+    }
+    case LWS_CALLBACK_HTTP_WRITEABLE: {
+      auto handler = dyn_handlers_[wsi].get();
+      if (!handler) {
+        LOG(WARNING) << "Unknown wsi became writable";
+        return 1;
+      }
+      handler->OnWritable();
+      // Make sure the connection (in HTTP 1) or stream (in HTTP 2) is closed
+      // after the response is written
+      return 1;
+    }
+    case LWS_CALLBACK_CLOSED_HTTP:
+      dyn_handlers_.erase(wsi);
+      break;
+    default:
+      return lws_callback_http_dummy(wsi, reason, user, in, len);
+  }
+  return 0;
+}
+
+int WebSocketServer::ServerCallback(struct lws* wsi,
+                                    enum lws_callback_reasons reason,
                                     void* user, void* in, size_t len) {
   switch (reason) {
     case LWS_CALLBACK_ESTABLISHED: {
@@ -156,8 +355,10 @@
     case LWS_CALLBACK_RECEIVE: {
       auto handler = handlers_[wsi];
       if (handler) {
+        bool is_final = (lws_remaining_packet_payload(wsi) == 0) &&
+                        lws_is_final_fragment(wsi);
         handler->OnReceive(reinterpret_cast<const uint8_t*>(in), len,
-                           lws_frame_is_binary(wsi));
+                           lws_frame_is_binary(wsi), is_final);
       } else {
         LOG(WARNING) << "Unkwnown wsi sent data";
       }
@@ -181,4 +382,16 @@
   }
 }
 
+std::unique_ptr<DynHandler> WebSocketServer::InstantiateDynHandler(
+    const std::string& uri_path, struct lws* wsi) {
+  auto it = dyn_handler_factories_.find(uri_path);
+  if (it == dyn_handler_factories_.end()) {
+    LOG(ERROR) << "Wrong path provided in URI: " << uri_path;
+    return nullptr;
+  } else {
+    LOG(INFO) << "Creating handler for " << uri_path;
+    return it->second->Build(wsi);
+  }
+}
+
 }  // namespace cuttlefish
diff --git a/host/libs/websocket/websocket_server.h b/host/libs/websocket/websocket_server.h
index 0695b3b..877a9c7 100644
--- a/host/libs/websocket/websocket_server.h
+++ b/host/libs/websocket/websocket_server.h
@@ -18,6 +18,7 @@
 
 #include <string>
 #include <unordered_map>
+#include <vector>
 
 #include <android-base/logging.h>
 #include <libwebsockets.h>
@@ -27,32 +28,63 @@
 namespace cuttlefish {
 class WebSocketServer {
  public:
-  WebSocketServer(
-    const char* protocol_name,
-    const std::string &certs_dir,
-    const std::string &assets_dir,
-    int port);
+  // Uses HTTP and WS
+  WebSocketServer(const char* protocol_name, const std::string& assets_dir,
+                  int port);
+  // Uses HTTPS and WSS when a certificates directory is provided
+  WebSocketServer(const char* protocol_name, const std::string& certs_dir,
+                  const std::string& assets_dir, int port);
   ~WebSocketServer() = default;
 
+  // Register a handler factory for websocket connections. A new handler will be
+  // created for each new websocket connection.
   void RegisterHandlerFactory(
-    const std::string &path,
-    std::unique_ptr<WebSocketHandlerFactory> handler_factory_p);
+      const std::string& path,
+      std::unique_ptr<WebSocketHandlerFactory> handler_factory_p);
+
+  // Register a handler factory for dynamic HTTP requests. A new handler will be
+  // created for each HTTP request.
+  void RegisterDynHandlerFactory(
+      const std::string& path,
+      std::unique_ptr<DynHandlerFactory> handler_factory_p);
+
   void Serve();
 
-
  private:
-  static std::unordered_map<struct lws*, std::shared_ptr<WebSocketHandler>> handlers_;
-  static std::unordered_map<std::string, std::unique_ptr<WebSocketHandlerFactory>>
-      handler_factories_;
+  static int WebsocketCallback(struct lws* wsi,
+                               enum lws_callback_reasons reason, void* user,
+                               void* in, size_t len);
 
-  static std::string GetPath(struct lws* wsi);
-  static int ServerCallback(struct lws* wsi, enum lws_callback_reasons reason,
+  static int DynHttpCallback(struct lws* wsi, enum lws_callback_reasons reason,
+                             void* user, void* in, size_t len);
+
+  int ServerCallback(struct lws* wsi, enum lws_callback_reasons reason,
                             void* user, void* in, size_t len);
-  static std::shared_ptr<WebSocketHandler> InstantiateHandler(
+  int DynServerCallback(struct lws* wsi,
+                               enum lws_callback_reasons reason, void* user,
+                               void* in, size_t len);
+  std::shared_ptr<WebSocketHandler> InstantiateHandler(
+      const std::string& uri_path, struct lws* wsi);
+  std::unique_ptr<DynHandler> InstantiateDynHandler(
       const std::string& uri_path, struct lws* wsi);
 
+  void InitializeLwsObjects();
+
+  std::unordered_map<struct lws*, std::shared_ptr<WebSocketHandler>> handlers_ =
+      {};
+  std::unordered_map<std::string, std::unique_ptr<WebSocketHandlerFactory>>
+      handler_factories_ = {};
+  std::unordered_map<struct lws*, std::unique_ptr<DynHandler>> dyn_handlers_ =
+      {};
+  std::unordered_map<std::string, std::unique_ptr<DynHandlerFactory>>
+      dyn_handler_factories_ = {};
+  std::string protocol_name_;
+  std::string assets_dir_;
+  std::string certs_dir_;
+  int server_port_;
   struct lws_context* context_;
-  struct lws_http_mount mount_;
+  struct lws_http_mount static_mount_;
+  std::vector<struct lws_http_mount> dyn_mounts_ = {};
   struct lws_protocol_vhost_options headers_;
   lws_retry_bo_t retry_;
 };
diff --git a/shared/BoardConfig.mk b/shared/BoardConfig.mk
index 7820cac..ab0b5a8 100644
--- a/shared/BoardConfig.mk
+++ b/shared/BoardConfig.mk
@@ -144,9 +144,16 @@
 USE_OPENGL_RENDERER := true
 
 # Wifi.
+ifeq ($(PRODUCT_ENFORCE_MAC80211_HWSIM),true)
+BOARD_WLAN_DEVICE           := emulator
+BOARD_HOSTAPD_PRIVATE_LIB   := lib_driver_cmd_simulated_cf
+WIFI_HIDL_FEATURE_DUAL_INTERFACE := true
+else
 BOARD_WLAN_DEVICE           := wlan0
+endif
 BOARD_HOSTAPD_DRIVER        := NL80211
 BOARD_WPA_SUPPLICANT_DRIVER := NL80211
+BOARD_WPA_SUPPLICANT_PRIVATE_LIB := lib_driver_cmd_simulated_cf
 WPA_SUPPLICANT_VERSION      := VER_0_8_X
 WIFI_DRIVER_FW_PATH_PARAM   := "/dev/null"
 WIFI_DRIVER_FW_PATH_STA     := "/dev/null"
@@ -159,7 +166,7 @@
 BOARD_SEPOLICY_DIRS += system/bt/vendor_libs/linux/sepolicy
 
 # Avoid multiple includes of sepolicy already included by Pixel experience.
-ifneq ($(filter aosp_% %_auto %_tv,$(PRODUCT_NAME)),)
+ifneq ($(filter aosp_% %_auto %_go_phone trout_% %_tv,$(PRODUCT_NAME)),)
 
 SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS += hardware/google/pixel-sepolicy/flipendo
 
@@ -209,7 +216,7 @@
 BOARD_KERNEL_CMDLINE += firmware_class.path=/vendor/etc/
 
 BOARD_KERNEL_CMDLINE += init=/init
-BOARD_BOOTCONFIG += hardware=cutf_cvm
+BOARD_BOOTCONFIG += androidboot.hardware=cutf_cvm
 
 # TODO(b/179489292): Remove once kfence is enabled everywhere
 BOARD_KERNEL_CMDLINE += kfence.sample_interval=500
@@ -219,8 +226,6 @@
 # TODO(b/182417593): Move all of these module options to modules.options
 # TODO(b/176860479): Remove once goldfish and cuttlefish share a wifi implementation
 BOARD_BOOTCONFIG += kernel.mac80211_hwsim.radios=0
-# TODO(b/175151042): Remove once we are using virtio-snd on cuttlefish
-BOARD_BOOTCONFIG += kernel.snd-hda-intel.enable=0
 # Reduce slab size usage from virtio vsock to reduce slab fragmentation
 BOARD_BOOTCONFIG += \
     kernel.vmw_vsock_virtio_transport_common.virtio_transport_max_vsock_pkt_buf_size=16384
@@ -253,7 +258,6 @@
   BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT := true
 endif
 BOARD_MOVE_GSI_AVB_KEYS_TO_VENDOR_BOOT := true
-BOARD_KERNEL_MODULE_INTERFACE_VERSIONS := 5.10-android12-0
 
 BOARD_GENERIC_RAMDISK_KERNEL_MODULES_LOAD := dm-user.ko
 
diff --git a/shared/config/grub.cfg b/shared/config/grub.cfg
index 958c6a3..d15eb8e 100644
--- a/shared/config/grub.cfg
+++ b/shared/config/grub.cfg
@@ -4,7 +4,7 @@
 # These options are accessible to chain-loaded configurations as well:
 #
 # pnpacpi=off      Disable on QEMU; allows serdev to claim platform serial
-# pci=noacpi       Crosvm doesn't support ACPI-based PCI enumeration
+# acpi=noirq       Do not configure IRQ routing using ACPI tables
 # reboot=k         Reboot using keyboard method, rather than ACPI
 # noexec=off       Some kernels panic when setting up NX
 # noefi            Some kernels panic when trying to use U-Boot EFI
@@ -13,7 +13,7 @@
 # console=ttyAMA0  QEMU on ARM64 uses alternative serial implementation
 #
 if [ "$grub_cpu" = "i386" ]; then
-  set cmdline="pnpacpi=off pci=noacpi reboot=k noexec=off console=ttyS0 noefi panic=-1 console=hvc0 snd-hda-intel.enable=0"
+  set cmdline="pnpacpi=off acpi=noirq reboot=k noexec=off console=ttyS0 noefi panic=-1 console=hvc0"
 elif [ "$grub_cpu" = "arm64" ]; then
   set cmdline="console=ttyS0 console=ttyAMA0 noefi panic=-1 console=hvc0"
 else
@@ -21,7 +21,7 @@
 fi
 
 # Root filesystem is on a GUID partition with label "otheros_root"
-set rootfs="PARTLABEL=otheros_root"
+set rootfs="/dev/vda14"
 
 # Root filesystem with grub installed
 search --file --set root /boot/grub/grub.cfg --hint (hd0)
diff --git a/shared/config/init.insmod.sh b/shared/config/init.insmod.sh
deleted file mode 100755
index ccbf716..0000000
--- a/shared/config/init.insmod.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/vendor/bin/sh
-
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-KERNEL_VERSION_NUMBER=`uname -r`
-MAINLINE_STR='mainline'
-if [[ $KERNEL_VERSION_NUMBER == *$MAINLINE_STR* ]]; then
-    IS_MAINLINE=1
-else
-    IS_MAINLINE=0
-fi
-
-KERNEL_VERSION_NUMBER=`echo $KERNEL_VERSION_NUMBER | grep -o -E '^[0-9]+\.[0-9]+'`
-# This folder on cuttlefish contains modules for multiple kernel versions.
-# Hence the need to filter them instead of relying on module.order
-VENDOR_MODULES='/vendor/lib/modules/*.ko'
-
-for f in $VENDOR_MODULES
-do
-    MOD_VERSION=`modinfo $f`
-    MOD_VERSION=`echo $MOD_VERSION | grep -o -E 'vermagic: [0-9a-zA-Z\.-]+'`
-    MOD_VERSION_NUMBER=`echo $MOD_VERSION | grep -o -E '[0-9]+\.[0-9]+'`
-    if [[ $MOD_VERSION == *$MAINLINE_STR* ]]; then
-        IS_MOD_MAINLINE=1
-    else
-        IS_MOD_MAINLINE=0
-    fi
-
-    # TODO (137683279) When we have a few more kernel modules, we'll have to do the module
-    # insertion of least dependencies.
-    if [ $IS_MOD_MAINLINE -eq $IS_MAINLINE ] && [ $MOD_VERSION_NUMBER == $KERNEL_VERSION_NUMBER ]
-    then
-        `insmod $f`
-        echo "Insmod " $f
-    fi
-done
diff --git a/shared/config/init.recovery.rc b/shared/config/init.recovery.rc
index 6f9ca141..3fdd4b0 100644
--- a/shared/config/init.recovery.rc
+++ b/shared/config/init.recovery.rc
@@ -3,8 +3,8 @@
     console
     disabled
     user root
-    group shell log readproc
-    seclabel u:r:shell:s0
+    group root shell log readproc
+    seclabel u:r:su:s0
     setenv HOSTNAME console
 
 on property:ro.debuggable=1
diff --git a/shared/config/init.vendor.rc b/shared/config/init.vendor.rc
index 184833f..38c89a3 100644
--- a/shared/config/init.vendor.rc
+++ b/shared/config/init.vendor.rc
@@ -5,6 +5,7 @@
 
     setprop ro.sf.lcd_density ${ro.boot.lcd_density}
     setprop ro.hardware.egl ${ro.boot.hardware.egl}
+    setprop debug.sf.vsync_reactor_ignore_present_fences true
     setprop ro.hardware.gralloc ${ro.boot.hardware.gralloc}
     setprop ro.hardware.hwcomposer ${ro.boot.hardware.hwcomposer}
     setprop ro.hardware.vulkan ${ro.boot.hardware.vulkan}
@@ -12,7 +13,7 @@
     setprop ro.hw_timeout_multiplier ${ro.boot.hw_timeout_multiplier}
 
     # start module load in the background
-    start vendor.insmod_sh
+    start vendor.dlkm_loader
 
 on init
     # ZRAM setup
@@ -46,15 +47,17 @@
     mount_all --early
     restorecon_recursive /vendor
 
-    start setup_wifi
     # works around framework netiface enumeration issue
-    start rename_eth1
+    start rename_eth0
 
     start bt_vhci_forwarder
 
     # So GceBootReporter can print to kmsg
     chmod 622 /dev/kmsg
 
+on fs && property:ro.vendor.wifi_impl=virt_wifi
+    start setup_wifi
+
 on post-fs
     # set RLIMIT_MEMLOCK to 64MB
     setrlimit 8 67108864 67108864
@@ -64,20 +67,29 @@
     mkdir /data/vendor/radio 0777 system system
 
 on late-fs
-    # Wait for keymaster
-    exec_start wait_for_keymaster
-
     # Mount RW partitions which need run fsck
     mount_all --late
 
     write /dev/kmsg "GUEST_BUILD_FINGERPRINT: ${ro.build.fingerprint}"
 
+on post-fs-data && property:ro.vendor.wifi_impl=mac8011_hwsim_virtio
+    setprop vold.post_fs_data_done 1
+    start wifi-net-sh
+
 on boot
     chmod 0660 /dev/cpuctl
     mkdir /data/vendor/wifi 0770 wifi wifi
     mkdir /data/vendor/wifi/wpa 0770 wifi wifi
     mkdir /data/vendor/wifi/wpa/sockets 0770 wifi wifi
     start socket_vsock_proxy
+    setprop ro.hardware.audio.primary goldfish
+
+service wifi-net-sh /vendor/bin/init.wifi.sh
+    class late_start
+    user root
+    group root wakelock wifi
+    oneshot
+    disabled    # Started on post-fs-data
 
 service bt_vhci_forwarder /vendor/bin/bt_vhci_forwarder -virtio_console_dev=/dev/hvc5
     user bluetooth
@@ -86,7 +98,7 @@
 service setup_wifi /vendor/bin/setup_wifi
     oneshot
 
-service rename_eth1 /vendor/bin/rename_netiface eth1 rmnet0
+service rename_eth0 /vendor/bin/rename_netiface eth0 rmnet0
     oneshot
 
 on property:sys.boot_completed=1
@@ -99,7 +111,7 @@
 on sys-boot-completed-set && property:persist.sys.zram_enabled=1
     swapon_all
 
-service vendor.insmod_sh /vendor/bin/init.insmod.sh
+service vendor.dlkm_loader /vendor/bin/dlkm_loader
     class main
     user root
     group root system
diff --git a/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_0.idc b/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_0.idc
new file mode 100644
index 0000000..8a03307
--- /dev/null
+++ b/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_0.idc
@@ -0,0 +1,6 @@
+device.internal = 1
+
+touch.deviceType = touchScreen
+touch.orientationAware = 1
+
+# touch.displayId = local:8141241408256768
diff --git a/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_1.idc b/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_1.idc
new file mode 100644
index 0000000..1e57818
--- /dev/null
+++ b/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_1.idc
@@ -0,0 +1,6 @@
+device.internal = 1
+
+touch.deviceType = touchScreen
+touch.orientationAware = 1
+
+touch.displayId = local:8141533520759297
diff --git a/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_2.idc b/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_2.idc
new file mode 100644
index 0000000..c9d6a8f
--- /dev/null
+++ b/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_2.idc
@@ -0,0 +1,6 @@
+device.internal = 1
+
+touch.deviceType = touchScreen
+touch.orientationAware = 1
+
+touch.displayId = local:8141106354454786
diff --git a/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_3.idc b/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_3.idc
new file mode 100644
index 0000000..a133e24
--- /dev/null
+++ b/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_3.idc
@@ -0,0 +1,6 @@
+device.internal = 1
+
+touch.deviceType = touchScreen
+touch.orientationAware = 1
+
+touch.displayId = local:8141521668171267
diff --git a/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_4.idc b/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_4.idc
new file mode 100644
index 0000000..c32fb3c
--- /dev/null
+++ b/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_4.idc
@@ -0,0 +1,6 @@
+device.internal = 1
+
+touch.deviceType = touchScreen
+touch.orientationAware = 1
+
+touch.displayId = local:8141140893513220
diff --git a/shared/config/manifest.xml b/shared/config/manifest.xml
index b14b82c..d48ac0b 100644
--- a/shared/config/manifest.xml
+++ b/shared/config/manifest.xml
@@ -16,16 +16,7 @@
 ** limitations under the License.
 */
 -->
-<manifest version="1.0" type="device" target-level="6">
-    <hal format="hidl">
-        <name>android.hardware.audio</name>
-        <transport>hwbinder</transport>
-        <version>6.0</version>
-        <interface>
-            <name>IDevicesFactory</name>
-            <instance>default</instance>
-        </interface>
-    </hal>
+<manifest version="1.0" type="device" target-level="7">
     <hal format="hidl">
         <name>android.hardware.audio.effect</name>
         <transport>hwbinder</transport>
@@ -97,11 +88,10 @@
         </interface>
     </hal>
     -->
-    <!-- TODO (b/130079341): -->
     <hal format="hidl">
         <name>android.hardware.graphics.composer</name>
         <transport>hwbinder</transport>
-        <version>2.2</version>
+        <version>2.3</version>
         <interface>
             <name>IComposer</name>
             <instance>default</instance>
diff --git a/shared/config/ueventd.rc b/shared/config/ueventd.rc
index 3d5a219..8f24201 100644
--- a/shared/config/ueventd.rc
+++ b/shared/config/ueventd.rc
@@ -17,7 +17,7 @@
 /dev/hvc2 0660 system logd
 
 # keymaster
-/dev/hvc3 0660 system system
+/dev/hvc3 0666 system system
 
 # gatekeeper
 /dev/hvc4 0660 system system
diff --git a/shared/device.mk b/shared/device.mk
index 5b319cb..70753ba 100644
--- a/shared/device.mk
+++ b/shared/device.mk
@@ -26,12 +26,21 @@
 # Enforce generic ramdisk allow list
 $(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
 
-PRODUCT_SOONG_NAMESPACES += device/generic/goldfish-opengl # for vulkan
+# Set Vendor SPL to match platform
+VENDOR_SECURITY_PATCH = $(PLATFORM_SECURITY_PATCH)
 
-PRODUCT_SHIPPING_API_LEVEL := 31
+# Set boot SPL
+BOOT_SECURITY_PATCH = $(PLATFORM_SECURITY_PATCH)
+
+PRODUCT_SOONG_NAMESPACES += device/generic/goldfish-opengl # for vulkan
+PRODUCT_SOONG_NAMESPACES += device/generic/goldfish # for audio and wifi
+
+PRODUCT_SHIPPING_API_LEVEL := 32
 PRODUCT_USE_DYNAMIC_PARTITIONS := true
 DISABLE_RILD_OEM_HOOK := true
 
+PRODUCT_SET_DEBUGFS_RESTRICTIONS := true
+
 PRODUCT_SOONG_NAMESPACES += device/generic/goldfish-opengl # for vulkan
 
 TARGET_RO_FILE_SYSTEM_TYPE ?= ext4
@@ -42,6 +51,9 @@
 TARGET_ENABLE_HOST_BLUETOOTH_EMULATION ?= true
 TARGET_USE_BTLINUX_HAL_IMPL ?= true
 
+# TODO(b/65201432): Swiftshader needs to create executable memory.
+PRODUCT_REQUIRES_INSECURE_EXECMEM_FOR_SWIFTSHADER := true
+
 AB_OTA_UPDATER := true
 AB_OTA_PARTITIONS += \
     boot \
@@ -57,7 +69,7 @@
     vendor_dlkm \
 
 # Enable Virtual A/B
-$(call inherit-product, $(SRC_TARGET_DIR)/product/virtual_ab_ota/compression.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/virtual_ab_ota/compression_with_xor.mk)
 
 # Enable Scoped Storage related
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulated_storage.mk)
@@ -135,6 +147,9 @@
 # DRM service opt-in
 PRODUCT_VENDOR_PROPERTIES += drm.service.enabled=true
 
+# Call deleteAllKeys if vold detects a factory reset
+PRODUCT_VENDOR_PROPERTIES += ro.crypto.metadata_init_delete_all_keys.enabled=true
+
 PRODUCT_SOONG_NAMESPACES += hardware/google/camera
 PRODUCT_SOONG_NAMESPACES += hardware/google/camera/devices/EmulatedCamera
 
@@ -143,9 +158,8 @@
 #
 PRODUCT_PACKAGES += \
     CuttlefishService \
-    cuttlefish_rotate \
+    cuttlefish_sensor_injection \
     rename_netiface \
-    setup_wifi \
     bt_vhci_forwarder \
     socket_vsock_proxy \
     tombstone_transmit \
@@ -189,12 +203,6 @@
     libGLESv2_angle \
     libfeature_support_angle.so
 
-# SwiftShader provides a software-only implementation that is not thread-safe
-PRODUCT_PACKAGES += \
-    libEGL_swiftshader \
-    libGLESv1_CM_swiftshader \
-    libGLESv2_swiftshader
-
 # GL implementation for virgl
 PRODUCT_PACKAGES += \
     libGLES_mesa \
@@ -220,14 +228,17 @@
     libEGL_emulation \
     libGLESv2_enc \
     libGLESv2_emulation \
-    libGLESv1_enc
+    libGLESv1_enc \
+    libGoldfishProfiler \
 
 #
 # Packages for testing
 #
 PRODUCT_PACKAGES += \
     aidl_lazy_test_server \
-    hidl_lazy_test_server
+    aidl_lazy_cb_test_server \
+    hidl_lazy_test_server \
+    hidl_lazy_cb_test_server
 
 DEVICE_PACKAGE_OVERLAYS := device/google/cuttlefish/shared/overlay
 # PRODUCT_AAPT_CONFIG and PRODUCT_AAPT_PREF_CONFIG are intentionally not set to
@@ -260,7 +271,7 @@
     hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_depth.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_depth.json \
     device/google/cuttlefish/shared/config/init.vendor.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/init.cutf_cvm.rc \
     device/google/cuttlefish/shared/config/init.product.rc:$(TARGET_COPY_OUT_PRODUCT)/etc/init/init.rc \
-    device/google/cuttlefish/shared/config/ueventd.rc:$(TARGET_COPY_OUT_VENDOR)/ueventd.rc \
+    device/google/cuttlefish/shared/config/ueventd.rc:$(TARGET_COPY_OUT_VENDOR)/etc/ueventd.rc \
     device/google/cuttlefish/shared/config/media_codecs.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs.xml \
     device/google/cuttlefish/shared/config/media_codecs_google_video.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_video.xml \
     device/google/cuttlefish/shared/config/media_codecs_performance.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_performance.xml \
@@ -292,28 +303,25 @@
     frameworks/native/data/etc/android.software.sip.voip.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.sip.voip.xml \
     frameworks/native/data/etc/android.software.verified_boot.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.verified_boot.xml \
     system/bt/vendor_libs/test_vendor_lib/data/controller_properties.json:vendor/etc/bluetooth/controller_properties.json \
-    device/google/cuttlefish/shared/config/task_profiles.json:$(TARGET_COPY_OUT_VENDOR)/etc/task_profiles.json
+    device/google/cuttlefish/shared/config/task_profiles.json:$(TARGET_COPY_OUT_VENDOR)/etc/task_profiles.json \
+    device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_0.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_0.idc \
+    device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_1.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_1.idc \
+    device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_2.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_2.idc \
+    device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_3.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_3.idc \
+    device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_4.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_4.idc
 
 ifeq ($(TARGET_RO_FILE_SYSTEM_TYPE),ext4)
 PRODUCT_COPY_FILES += \
     device/google/cuttlefish/shared/config/fstab.f2fs:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.f2fs \
-    device/google/cuttlefish/shared/config/fstab.f2fs:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/fstab.f2fs \
     device/google/cuttlefish/shared/config/fstab.f2fs:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.f2fs \
-    device/google/cuttlefish/shared/config/fstab.f2fs:$(TARGET_COPY_OUT_RECOVERY)/root/first_stage_ramdisk/fstab.f2fs \
     device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.ext4 \
-    device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/fstab.ext4 \
-    device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.ext4 \
-    device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_RECOVERY)/root/first_stage_ramdisk/fstab.ext4
+    device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.ext4
 else
 PRODUCT_COPY_FILES += \
     device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).f2fs:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.f2fs \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).f2fs:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/fstab.f2fs \
     device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).f2fs:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.f2fs \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).f2fs:$(TARGET_COPY_OUT_RECOVERY)/root/first_stage_ramdisk/fstab.f2fs \
     device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.ext4 \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/fstab.ext4 \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.ext4 \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_RECOVERY)/root/first_stage_ramdisk/fstab.ext4
+    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.ext4
 endif
 
 ifeq ($(TARGET_VULKAN_SUPPORT),true)
@@ -362,8 +370,8 @@
     hwcomposer.drm_minigbm \
     hwcomposer.cutf \
     hwcomposer-stats \
-    android.hardware.graphics.composer@2.2-impl \
-    android.hardware.graphics.composer@2.2-service
+    android.hardware.graphics.composer@2.3-impl \
+    android.hardware.graphics.composer@2.3-service
 
 #
 # Gralloc HAL
@@ -396,16 +404,17 @@
 # Audio HAL
 #
 LOCAL_AUDIO_PRODUCT_PACKAGE ?= \
-    audio.primary.cutf \
-    audio.r_submix.default \
-    android.hardware.audio@6.0-impl \
+    android.hardware.audio.service \
+    android.hardware.audio@6.0-impl.ranchu \
     android.hardware.audio.effect@6.0-impl \
-    android.hardware.audio@2.0-service
 
 LOCAL_AUDIO_PRODUCT_COPY_FILES ?= \
-    device/google/cuttlefish/shared/config/audio_policy.conf:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy.conf \
-    frameworks/av/services/audiopolicy/config/audio_policy_configuration_generic.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_configuration.xml \
-    frameworks/av/services/audiopolicy/config/primary_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/primary_audio_policy_configuration.xml
+    device/generic/goldfish/audio/policy/audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_configuration.xml \
+    device/generic/goldfish/audio/policy/primary_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/primary_audio_policy_configuration.xml \
+    frameworks/av/services/audiopolicy/config/r_submix_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/r_submix_audio_policy_configuration.xml \
+    frameworks/av/services/audiopolicy/config/audio_policy_volumes.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_volumes.xml \
+    frameworks/av/services/audiopolicy/config/default_volume_tables.xml:$(TARGET_COPY_OUT_VENDOR)/etc/default_volume_tables.xml \
+    frameworks/av/media/libeffects/data/audio_effects.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_effects.xml \
 
 LOCAL_AUDIO_DEVICE_PACKAGE_OVERLAYS ?=
 
@@ -449,16 +458,24 @@
 #
 # Camera
 #
+ifeq ($(TARGET_USE_VSOCK_CAMERA_HAL_IMPL),true)
+PRODUCT_PACKAGES += \
+    android.hardware.camera.provider@2.6-external-vsock-service \
+    android.hardware.camera.provider@2.6-impl-cuttlefish
+DEVICE_MANIFEST_FILE += \
+    device/google/cuttlefish/guest/hals/camera/manifest.xml
+else
 PRODUCT_PACKAGES += \
     android.hardware.camera.provider@2.6-service-google \
     libgooglecamerahwl_impl \
     android.hardware.camera.provider@2.6-impl-google \
 
+endif
 #
 # Gatekeeper
 #
 ifeq ($(LOCAL_GATEKEEPER_PRODUCT_PACKAGE),)
-       LOCAL_GATEKEEPER_PRODUCT_PACKAGE := android.hardware.gatekeeper@1.0-service.software
+       LOCAL_GATEKEEPER_PRODUCT_PACKAGE := android.hardware.gatekeeper@1.0-service.remote
 endif
 PRODUCT_PACKAGES += \
     $(LOCAL_GATEKEEPER_PRODUCT_PACKAGE)
@@ -510,22 +527,17 @@
     android.hardware.lights-service.example \
 
 #
-# Keymaster HAL
-#
-ifeq ($(LOCAL_KEYMASTER_PRODUCT_PACKAGE),)
-       LOCAL_KEYMASTER_PRODUCT_PACKAGE := android.hardware.keymaster@4.1-service
-endif
-PRODUCT_PACKAGES += \
-    $(LOCAL_KEYMASTER_PRODUCT_PACKAGE)
-
-#
 # KeyMint HAL
 #
 ifeq ($(LOCAL_KEYMINT_PRODUCT_PACKAGE),)
-       LOCAL_KEYMINT_PRODUCT_PACKAGE := android.hardware.security.keymint-service
+       LOCAL_KEYMINT_PRODUCT_PACKAGE := android.hardware.security.keymint-service.remote
 endif
-# PRODUCT_PACKAGES += \
-#    $(LOCAL_KEYMINT_PRODUCT_PACKAGE)
+ PRODUCT_PACKAGES += \
+    $(LOCAL_KEYMINT_PRODUCT_PACKAGE)
+
+# Keymint configuration
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/android.software.device_id_attestation.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.device_id_attestation.xml
 
 #
 # Power HAL
@@ -553,7 +565,6 @@
     android.hardware.neuralnetworks-service-sample-float-slow \
     android.hardware.neuralnetworks-service-sample-minimal \
     android.hardware.neuralnetworks-service-sample-quant \
-    android.hardware.neuralnetworks-shell-service-sample \
     android.hardware.neuralnetworks-shim-service-sample
 
 #
@@ -579,14 +590,6 @@
 PRODUCT_PACKAGES += \
     android.hardware.memtrack-service.example
 
-# GKI APEX
-PRODUCT_PACKAGES += com.android.gki.kmi_5_10_android12_0
-
-# Prevent GKI and boot image downgrades
-PRODUCT_PRODUCT_PROPERTIES += \
-    ro.build.ab_update.gki.prevent_downgrade_version=true \
-    ro.build.ab_update.gki.prevent_downgrade_spl=true \
-
 # WLAN driver configuration files
 PRODUCT_COPY_FILES += \
     external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant_template.conf:$(TARGET_COPY_OUT_VENDOR)/etc/wifi/wpa_supplicant.conf \
@@ -617,11 +620,21 @@
 PRODUCT_PACKAGES += linker.recovery shell_and_utilities_recovery
 endif
 
-#
-# Shell script Vendor Module Loading
-#
+# wifi
+ifeq ($(PRODUCT_ENFORCE_MAC80211_HWSIM),true)
+PRODUCT_PACKAGES += \
+    mac80211_create_radios \
+    hostapd \
+    android.hardware.wifi@1.0-service
+
 PRODUCT_COPY_FILES += \
-   $(LOCAL_PATH)/config/init.insmod.sh:$(TARGET_COPY_OUT_VENDOR)/bin/init.insmod.sh \
+    device/google/cuttlefish/guest/services/wifi/init.wifi.sh:$(TARGET_COPY_OUT_VENDOR)/bin/init.wifi.sh \
+
+PRODUCT_VENDOR_PROPERTIES += ro.vendor.wifi_impl=mac8011_hwsim_virtio
+else
+PRODUCT_PACKAGES += setup_wifi
+PRODUCT_VENDOR_PROPERTIES += ro.vendor.wifi_impl=virt_wifi
+endif
 
 # Host packages to install
 PRODUCT_HOST_PACKAGES += socket_vsock_proxy
@@ -634,3 +647,6 @@
 # with HW VSYNC
 PRODUCT_VENDOR_PROPERTIES += \
     ro.surface_flinger.running_without_sync_framework=true
+# Vendor Dlkm Locader
+PRODUCT_PACKAGES += \
+   dlkm_loader
diff --git a/shared/permissions/cuttlefish_excluded_hardware.xml b/shared/permissions/cuttlefish_excluded_hardware.xml
index 3660289..c3d03d5 100644
--- a/shared/permissions/cuttlefish_excluded_hardware.xml
+++ b/shared/permissions/cuttlefish_excluded_hardware.xml
@@ -14,7 +14,6 @@
      limitations under the License.
 -->
 <permissions>
-    <unavailable-feature name="android.hardware.microphone" />
     <unavailable-feature name="android.software.print" />
     <unavailable-feature name="android.software.voice_recognizers" />
 </permissions>
diff --git a/shared/phone/overlay/frameworks/base/core/res/res/values/config.xml b/shared/phone/overlay/frameworks/base/core/res/res/values/config.xml
index 7ae7588..b6554cc 100644
--- a/shared/phone/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/shared/phone/overlay/frameworks/base/core/res/res/values/config.xml
@@ -65,4 +65,7 @@
     <item>2:2:255</item> <!-- ID2:Fingerprint(HIDL):Weak -->
     <item>3:8:255</item> <!-- ID3:Face(HIDL):Weak -->
   </string-array>
+
+  <!-- Enable Night display, which requires HWC 2.0. -->
+  <bool name="config_nightDisplayAvailable">true</bool>
 </resources>
diff --git a/shared/sepolicy/system_ext/private/mediatranscoding.te b/shared/sepolicy/system_ext/private/mediatranscoding.te
index 22e006a..47f6d8e 100644
--- a/shared/sepolicy/system_ext/private/mediatranscoding.te
+++ b/shared/sepolicy/system_ext/private/mediatranscoding.te
@@ -1,8 +1,2 @@
-# Allow mediatranscoding service to access media-related system properties
-get_prop(mediatranscoding, media_config_prop)
-
-# Allow mediatranscoding service to use DMA-BUF
-allow mediatranscoding dmabuf_system_heap_device:chr_file r_file_perms;
-
 # Allow mediatranscoding service to access the GPU
 gpu_access(mediatranscoding)
diff --git a/shared/sepolicy/vendor/bluetooth.te b/shared/sepolicy/vendor/bluetooth.te
new file mode 100644
index 0000000..aa2671a
--- /dev/null
+++ b/shared/sepolicy/vendor/bluetooth.te
@@ -0,0 +1 @@
+gpu_access(bluetooth)
diff --git a/shared/sepolicy/vendor/bug_map b/shared/sepolicy/vendor/bug_map
index 32a0b4c..46ef531 100644
--- a/shared/sepolicy/vendor/bug_map
+++ b/shared/sepolicy/vendor/bug_map
@@ -2,4 +2,3 @@
 init system_lib_file file b/133444385
 kernel kernel capability b/179966921
 migrate_legacy_obb_data dalvikcache_data_file file b/152338071
-system_server system_server process b/65201432
diff --git a/shared/sepolicy/vendor/cuttlefish_rotate.te b/shared/sepolicy/vendor/cuttlefish_rotate.te
deleted file mode 100644
index 7d8a9a1..0000000
--- a/shared/sepolicy/vendor/cuttlefish_rotate.te
+++ /dev/null
@@ -1,15 +0,0 @@
-type cuttlefish_rotate, domain;
-type cuttlefish_rotate_exec, exec_type, vendor_file_type, file_type;
-
-# Switch to cuttlefish_rotate domain when executing from shell.
-domain_auto_trans(shell, cuttlefish_rotate_exec, cuttlefish_rotate)
-allow cuttlefish_rotate shell:fd use;
-
-# Allow cuttlefish_rotate to communicate over adb connection.
-allow cuttlefish_rotate adbd:fd use;
-allow cuttlefish_rotate adbd:unix_stream_socket { read write };
-# Needed to run the binary directly via adb socket.
-allow cuttlefish_rotate devpts:chr_file { read write };
-
-# Grant cuttlefish_rotate access to the ISensors HAL.
-hal_client_domain(cuttlefish_rotate, hal_sensors)
diff --git a/shared/sepolicy/vendor/cuttlefish_sensor_injection.te b/shared/sepolicy/vendor/cuttlefish_sensor_injection.te
new file mode 100644
index 0000000..9e7aca5
--- /dev/null
+++ b/shared/sepolicy/vendor/cuttlefish_sensor_injection.te
@@ -0,0 +1,15 @@
+type cuttlefish_sensor_injection, domain;
+type cuttlefish_sensor_injection_exec, exec_type, vendor_file_type, file_type;
+
+# Switch to cuttlefish_sensor_injection domain when executing from shell.
+domain_auto_trans(shell, cuttlefish_sensor_injection_exec, cuttlefish_sensor_injection)
+allow cuttlefish_sensor_injection shell:fd use;
+
+# Allow cuttlefish_sensor_injection to communicate over adb connection.
+allow cuttlefish_sensor_injection adbd:fd use;
+allow cuttlefish_sensor_injection adbd:unix_stream_socket { read write };
+# Needed to run the binary directly via adb socket.
+allow cuttlefish_sensor_injection devpts:chr_file { read write };
+
+# Grant cuttlefish_sensor_injection access to the ISensors HAL.
+hal_client_domain(cuttlefish_sensor_injection, hal_sensors)
diff --git a/shared/sepolicy/vendor/device.te b/shared/sepolicy/vendor/device.te
index b698e35..fe42c1f 100644
--- a/shared/sepolicy/vendor/device.te
+++ b/shared/sepolicy/vendor/device.te
@@ -1,2 +1,2 @@
 # Device types
-type ab_block_device, dev_type;
+type ab_block_device, dev_type, bdev_type;
diff --git a/shared/sepolicy/vendor/dlkm_loader.te b/shared/sepolicy/vendor/dlkm_loader.te
new file mode 100644
index 0000000..afdc65c
--- /dev/null
+++ b/shared/sepolicy/vendor/dlkm_loader.te
@@ -0,0 +1,16 @@
+type dlkm_loader, domain;
+type dlkm_loader_exec, exec_type, vendor_file_type, file_type;
+
+init_daemon_domain(dlkm_loader)
+
+# Allow insmod on vendor and system partitions
+allow dlkm_loader self:capability sys_module;
+allow dlkm_loader system_file:system module_load;
+allow dlkm_loader vendor_file:system module_load;
+
+# needed for libmodprobe to read kernel commandline
+allow dlkm_loader proc_cmdline:file r_file_perms;
+
+# dlkm_loader searches tracefs while looking for modules
+dontaudit dlkm_loader debugfs_bootreceiver_tracing:dir search;
+dontaudit dlkm_loader debugfs_mm_events_tracing:dir search;
diff --git a/shared/sepolicy/vendor/file_contexts b/shared/sepolicy/vendor/file_contexts
index 23f3f60..72362dc 100644
--- a/shared/sepolicy/vendor/file_contexts
+++ b/shared/sepolicy/vendor/file_contexts
@@ -56,12 +56,14 @@
 #############################
 # Vendor files
 #
-/vendor/bin/cuttlefish_rotate   u:object_r:cuttlefish_rotate_exec:s0
+/vendor/bin/cuttlefish_sensor_injection   u:object_r:cuttlefish_sensor_injection_exec:s0
+/vendor/bin/mac80211_create_radios u:object_r:mac80211_create_radios_exec:s0
 /vendor/bin/socket_vsock_proxy  u:object_r:socket_vsock_proxy_exec:s0
 /vendor/bin/vsoc_input_service  u:object_r:vsoc_input_service_exec:s0
 /vendor/bin/rename_netiface  u:object_r:rename_netiface_exec:s0
 /vendor/bin/suspend_blocker  u:object_r:suspend_blocker_exec:s0
 /vendor/bin/hw/libcuttlefish-rild  u:object_r:libcuttlefish_rild_exec:s0
+/vendor/bin/hw/android\.hardware\.camera\.provider@2\.6-external-vsock-service u:object_r:hal_camera_default_exec:s0
 /vendor/bin/hw/android\.hardware\.camera\.provider@2\.6-service-google u:object_r:hal_camera_default_exec:s0
 /vendor/bin/hw/android\.hardware\.camera\.provider@2\.6-service-google-lazy u:object_r:hal_camera_default_exec:s0
 /vendor/bin/hw/android\.hardware\.power\.stats@1\.0-service\.mock  u:object_r:hal_power_stats_default_exec:s0
@@ -77,7 +79,6 @@
 /vendor/bin/hw/android\.hardware\.health\.storage-service\.cuttlefish u:object_r:hal_health_storage_default_exec:s0
 /vendor/bin/hw/android\.hardware\.lights-service\.example u:object_r:hal_light_default_exec:s0
 /vendor/bin/hw/android\.hardware\.neuralnetworks@1\.3-service-sample-.*   u:object_r:hal_neuralnetworks_sample_exec:s0
-/vendor/bin/hw/android\.hardware\.neuralnetworks-shell-service-sample   u:object_r:hal_neuralnetworks_sample_exec:s0
 /vendor/bin/hw/android\.hardware\.neuralnetworks-shim-service-sample   u:object_r:hal_neuralnetworks_sample_exec:s0
 /vendor/bin/hw/android\.hardware\.neuralnetworks-service-sample-.*   u:object_r:hal_neuralnetworks_sample_exec:s0
 /vendor/bin/hw/android\.hardware\.vibrator@1\.x-service\.example u:object_r:hal_vibrator_default_exec:s0
@@ -87,6 +88,7 @@
 /vendor/bin/hw/android\.hardware\.sensors@2\.1-service\.mock  u:object_r:hal_sensors_default_exec:s0
 /vendor/bin/hw/android\.hardware\.input\.classifier@1\.0-service.default  u:object_r:hal_input_classifier_default_exec:s0
 /vendor/bin/hw/android\.hardware\.thermal@2\.0-service\.mock  u:object_r:hal_thermal_default_exec:s0
+/vendor/bin/hw/android\.hardware\.security\.keymint-service\.remote  u:object_r:hal_keymint_remote_exec:s0
 /vendor/bin/hw/android\.hardware\.keymaster@4\.1-service.remote  u:object_r:hal_keymaster_remote_exec:s0
 /vendor/bin/hw/android\.hardware\.gatekeeper@1\.0-service.remote  u:object_r:hal_gatekeeper_remote_exec:s0
 /vendor/bin/hw/android\.hardware\.oemlock-service.example u:object_r:hal_oemlock_default_exec:s0
@@ -94,12 +96,14 @@
 /vendor/bin/hw/android\.hardware\.authsecret@1\.0-service  u:object_r:hal_authsecret_default_exec:s0
 /vendor/bin/hw/android\.hardware\.authsecret-service.example u:object_r:hal_authsecret_default_exec:s0
 /vendor/bin/hw/android\.hardware\.rebootescrow-service\.default  u:object_r:hal_rebootescrow_default_exec:s0
-/vendor/bin/init\.insmod\.sh  u:object_r:init_insmod_sh_exec:s0
+/vendor/bin/dlkm_loader  u:object_r:dlkm_loader_exec:s0
+/vendor/bin/init\.wifi\.sh    u:object_r:init_wifi_sh_exec:s0
 
 /vendor/lib(64)?/libdrm.so  u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/libglapi.so  u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/dri/.* u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/hw/android\.hardware\.graphics\.mapper@4\.0-impl\.minigbm\.so u:object_r:same_process_hal_file:s0
+/vendor/lib(64)?/libminigbm_gralloc.so  u:object_r:same_process_hal_file: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)?/hw/vulkan.pastel.so  u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/libcuttlefish_fs.so  u:object_r:same_process_hal_file:s0
@@ -121,3 +125,4 @@
 /vendor/lib(64)?/libGLESv1_CM_angle\.so    u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/libGLESv2_angle\.so       u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/libfeature_support_angle\.so       u:object_r:same_process_hal_file:s0
+/vendor/lib(64)?/libGoldfishProfiler\.so       u:object_r:same_process_hal_file:s0
diff --git a/shared/sepolicy/vendor/genfs_contexts b/shared/sepolicy/vendor/genfs_contexts
index 22cb59f..ef41a43 100644
--- a/shared/sepolicy/vendor/genfs_contexts
+++ b/shared/sepolicy/vendor/genfs_contexts
@@ -30,17 +30,23 @@
 dnl
 # crosvm (x86)
 cf_pci_block_device(/devices/pci0000:00, 0x6, 5)
-cf_pci_gpu_device(/devices/pci0000:00, 0x11)
+cf_pci_gpu_device(/devices/pci0000:00, 0x12)
 ## find /sys/devices/platform/* -type d -name 'rtc[0-9]' | sed 's,/rtc[0-9],,'
 genfscon sysfs /devices/platform/rtc_cmos/rtc u:object_r:sysfs_rtc:s0
 ## find /sys/devices/platform/* -type d -name 'wakeup[0-9]'
-cf_rtc_wakeup_alarmtimer(/devices/platform/rtc_cmos, 0, 0)
-genfscon sysfs /devices/platform/rtc-test.1/wakeup/wakeup2 u:object_r:sysfs_wakeup:s0
-genfscon sysfs /devices/platform/rtc-test.2/wakeup/wakeup3 u:object_r:sysfs_wakeup:s0
+genfscon sysfs /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/wakeup/wakeup0 u:object_r:sysfs_wakeup:s0
+cf_rtc_wakeup_alarmtimer(/devices/platform/rtc_cmos, 0, 1) # wakeup{1,2}
+genfscon sysfs /devices/platform/rtc-test.1/wakeup/wakeup3 u:object_r:sysfs_wakeup:s0
+genfscon sysfs /devices/platform/rtc-test.2/wakeup/wakeup4 u:object_r:sysfs_wakeup:s0
+## currently disabled
+#genfscon sysfs /devices/LNXSYSTM:00/GFSH0001:00/wakeup/wakeup2 u:object_r:sysfs_wakeup:s0
+#genfscon sysfs /devices/platform/GFSH0001:00/power_supply u:object_r:sysfs_batteryinfo:s0
+#genfscon sysfs /devices/platform/GFSH0001:00/power_supply/ac/wakeup3 u:object_r:sysfs_wakeup:s0
+#genfscon sysfs /devices/platform/GFSH0001:00/power_supply/battery/wakeup4 u:object_r:sysfs_wakeup:s0
 
 # crosvm (arm64)
 cf_pci_block_device(/devices/platform/10000.pci, 0x6, 4)
-cf_pci_gpu_device(/devices/platform/10000.pci/pci0000:00, 0x11)
+cf_pci_gpu_device(/devices/platform/10000.pci/pci0000:00, 0x10)
 ## find /sys/devices/platform/* -type d -name 'rtc[0-9]' | sed 's,/rtc[0-9],,'
 genfscon sysfs /devices/platform/2000.rtc/rtc u:object_r:sysfs_rtc:s0
 ## find /sys/devices/platform/* -type d -name 'wakeup[0-9]'
@@ -67,7 +73,7 @@
 
 # qemu (arm)
 cf_pci_block_device(/devices/platform/3f000000.pcie/pci0000:00, 0x6, 4)
-cf_pci_gpu_device(/devices/platform/3f000000.pcie/pci0000:00, 0xf)
+cf_pci_gpu_device(/devices/platform/3f000000.pcie/pci0000:00, 0x11)
 
 # common on all platforms / vm managers
 genfscon sysfs /devices/platform/rtc-test.0/rtc u:object_r:sysfs_rtc:s0
diff --git a/shared/sepolicy/system_ext/private/mediaprovider.te b/shared/sepolicy/vendor/google/mediaprovider.te
similarity index 100%
rename from shared/sepolicy/system_ext/private/mediaprovider.te
rename to shared/sepolicy/vendor/google/mediaprovider.te
diff --git a/shared/sepolicy/system_ext/private/traceur_app.te b/shared/sepolicy/vendor/google/traceur_app.te
similarity index 100%
rename from shared/sepolicy/system_ext/private/traceur_app.te
rename to shared/sepolicy/vendor/google/traceur_app.te
diff --git a/shared/sepolicy/vendor/hal_camera_default.te b/shared/sepolicy/vendor/hal_camera_default.te
index 6bf571c..e4dac76 100644
--- a/shared/sepolicy/vendor/hal_camera_default.te
+++ b/shared/sepolicy/vendor/hal_camera_default.te
@@ -10,3 +10,6 @@
 hal_client_domain(hal_camera_default, hal_thermal)
 
 gpu_access(hal_camera_default)
+
+# Vsocket camera
+allow hal_camera_default self:vsock_socket { accept bind create getopt listen read write };
diff --git a/shared/sepolicy/vendor/hal_keymint_remote.te b/shared/sepolicy/vendor/hal_keymint_remote.te
new file mode 100644
index 0000000..578d1ad
--- /dev/null
+++ b/shared/sepolicy/vendor/hal_keymint_remote.te
@@ -0,0 +1,14 @@
+type hal_keymint_remote, domain;
+hal_server_domain(hal_keymint_remote, hal_keymint)
+
+type hal_keymint_remote_exec, exec_type, vendor_file_type, file_type;
+init_daemon_domain(hal_keymint_remote)
+
+allow hal_keymint_remote device:dir r_dir_perms;
+allow hal_keymint_remote keymaster_device:chr_file rw_file_perms;
+
+# Write to kernel log (/dev/kmsg)
+allow hal_keymint_remote kmsg_device:chr_file w_file_perms;
+allow hal_keymint_remote kmsg_device:chr_file getattr;
+
+get_prop(hal_keymint_remote, vendor_security_patch_level_prop)
diff --git a/shared/sepolicy/vendor/hal_wifi_default.te b/shared/sepolicy/vendor/hal_wifi_default.te
new file mode 100644
index 0000000..cde7ada
--- /dev/null
+++ b/shared/sepolicy/vendor/hal_wifi_default.te
@@ -0,0 +1,3 @@
+allow hal_wifi_default hal_wifi_default:netlink_route_socket {
+    create bind write read nlmsg_read nlmsg_readpriv };
+allow hal_wifi_default self:capability { sys_module };
diff --git a/shared/sepolicy/vendor/init_insmod_sh.te b/shared/sepolicy/vendor/init_insmod_sh.te
deleted file mode 100644
index 5400a37..0000000
--- a/shared/sepolicy/vendor/init_insmod_sh.te
+++ /dev/null
@@ -1,11 +0,0 @@
-type init_insmod_sh, domain;
-type init_insmod_sh_exec, exec_type, vendor_file_type, file_type;
-
-init_daemon_domain(init_insmod_sh)
-
-allow init_insmod_sh vendor_shell_exec:file rx_file_perms;
-allow init_insmod_sh vendor_toolbox_exec:file rx_file_perms;
-
-# Allow insmod
-allow init_insmod_sh self:capability sys_module;
-allow init_insmod_sh vendor_file:system module_load;
diff --git a/shared/sepolicy/vendor/init_wifi_sh.te b/shared/sepolicy/vendor/init_wifi_sh.te
new file mode 100644
index 0000000..331a745
--- /dev/null
+++ b/shared/sepolicy/vendor/init_wifi_sh.te
@@ -0,0 +1,14 @@
+# cuttlefish-setup service: runs init.cuttlefish.sh script
+type init_wifi_sh, domain;
+type init_wifi_sh_exec, vendor_file_type, exec_type, file_type;
+
+init_daemon_domain(init_wifi_sh)
+
+allow init_wifi_sh self:capability { fowner chown net_admin net_raw };
+allow init_wifi_sh vendor_toolbox_exec:file execute_no_trans;
+allow init_wifi_sh mac80211_create_radios_exec:file execute_no_trans;
+
+vendor_internal_prop(vendor_wifi_mac_prefix);
+get_prop(init_wifi_sh, vendor_wifi_mac_prefix);
+
+allow init_wifi_sh self:netlink_generic_socket create_socket_perms_no_ioctl;
diff --git a/shared/sepolicy/vendor/mac80211_create_radios.te b/shared/sepolicy/vendor/mac80211_create_radios.te
new file mode 100644
index 0000000..f7e6b7f
--- /dev/null
+++ b/shared/sepolicy/vendor/mac80211_create_radios.te
@@ -0,0 +1,2 @@
+type mac80211_create_radios, domain;
+type mac80211_create_radios_exec, exec_type, vendor_file_type, file_type;
diff --git a/shared/sepolicy/vendor/property_contexts b/shared/sepolicy/vendor/property_contexts
index ebbe271..fca72f7 100644
--- a/shared/sepolicy/vendor/property_contexts
+++ b/shared/sepolicy/vendor/property_contexts
@@ -9,6 +9,7 @@
 ro.boot.vsock_keyboard_port  u:object_r:vendor_vsock_keyboard_port:s0
 ro.boot.modem_simulator_ports  u:object_r:vendor_modem_simulator_ports_prop:s0
 ro.boot.vsock_touch_port  u:object_r:vendor_vsock_touch_port:s0
-ro.boot.wifi_mac_address  u:object_r:vendor_wifi_mac_address:s0
+ro.boot.wifi_mac_prefix  u:object_r:vendor_wifi_mac_prefix:s0 exact string
+ro.vendor.wifi_impl u:object_r:vendor_wifi_impl:s0 exact string
 vendor.bt.rootcanal_mac_address  u:object_r:vendor_bt_rootcanal_prop:s0
 vendor.bt.rootcanal_test_console  u:object_r:vendor_bt_rootcanal_prop:s0
diff --git a/shared/sepolicy/vendor/service_contexts b/shared/sepolicy/vendor/service_contexts
index 03b768c..d20d026 100644
--- a/shared/sepolicy/vendor/service_contexts
+++ b/shared/sepolicy/vendor/service_contexts
@@ -4,7 +4,6 @@
 android.hardware.neuralnetworks.IDevice/nnapi-sample_minimal    u:object_r:hal_neuralnetworks_service:s0
 android.hardware.neuralnetworks.IDevice/nnapi-sample_quant    u:object_r:hal_neuralnetworks_service:s0
 android.hardware.neuralnetworks.IDevice/nnapi-sample_sl_shim  u:object_r:hal_neuralnetworks_service:s0
-android.hardware.neuralnetworks.IDevice/nnapi-sample_sl_updatable  u:object_r:hal_neuralnetworks_service:s0
 
 # Binder service mappings
 gce                                       u:object_r:gce_service:s0
diff --git a/shared/sepolicy/vendor/setup_wifi.te b/shared/sepolicy/vendor/setup_wifi.te
index 23a34eb..b61d4be 100644
--- a/shared/sepolicy/vendor/setup_wifi.te
+++ b/shared/sepolicy/vendor/setup_wifi.te
@@ -9,6 +9,4 @@
 
 allow setup_wifi kernel:system module_request;
 
-vendor_internal_prop(vendor_wifi_mac_address)
-
-get_prop(setup_wifi, vendor_wifi_mac_address)
+get_prop(setup_wifi, vendor_wifi_mac_prefix)
diff --git a/shared/sepolicy/vendor/shell.te b/shared/sepolicy/vendor/shell.te
index 60d15fa..cc26032 100644
--- a/shared/sepolicy/vendor/shell.te
+++ b/shared/sepolicy/vendor/shell.te
@@ -1,5 +1,5 @@
 allow shell serial_device:chr_file { getattr ioctl read write };
-allow shell cuttlefish_rotate_exec:file rx_file_perms;
+allow shell cuttlefish_sensor_injection_exec:file rx_file_perms;
 
 # TODO(b/130668487): Label the vsock sockets.
 allow shell adbd:{ socket vsock_socket } rw_socket_perms_no_ioctl;
diff --git a/shared/sepolicy/vendor/system_server.te b/shared/sepolicy/vendor/system_server.te
index 372ca50..1df4c7d 100644
--- a/shared/sepolicy/vendor/system_server.te
+++ b/shared/sepolicy/vendor/system_server.te
@@ -1,11 +1,7 @@
-# TODO(b/65201432): Switch into enforcing mode once execmem issue due to OpenGL is resolved. Also
-# remove the corresponding dontaudit.
-# The current (at the time of writing) implementation of OpenGL needs to create executable memory.
-# Unfortunately, we cannot grant execmem power using an allow rule because global policy
-# (system/sepolicy) contains a corresponding neverallow which would cause build-time errors if the
-# allow execmem rule were added here.
-permissive system_server;
 gpu_access(system_server)
 
 # Cuttlefish is still using the legacy wifi HAL (pre-HIDL)
 get_prop(system_server, wifi_hal_prop)
+
+# TODO(b/65201432): Swiftshader needs to create executable memory.
+allow system_server self:process execmem;
diff --git a/shared/sepolicy/vendor/vendor_init.te b/shared/sepolicy/vendor/vendor_init.te
index 37e76e1..bcda4a3 100644
--- a/shared/sepolicy/vendor/vendor_init.te
+++ b/shared/sepolicy/vendor/vendor_init.te
@@ -7,3 +7,6 @@
 set_prop(vendor_init, vendor_bt_rootcanal_prop)
 
 get_prop(vendor_init, vendor_graphics_config_prop)
+
+vendor_internal_prop(vendor_wifi_impl)
+set_prop(vendor_init, vendor_wifi_impl)
diff --git a/shared/tv/device.mk b/shared/tv/device.mk
index 52e1e38..487c16a 100644
--- a/shared/tv/device.mk
+++ b/shared/tv/device.mk
@@ -28,6 +28,7 @@
     frameworks/native/data/etc/android.hardware.hdmi.cec.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.hdmi.cec.xml \
     frameworks/native/data/etc/android.hardware.sensor.accelerometer.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.accelerometer.xml \
     frameworks/native/data/etc/android.hardware.sensor.compass.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.compass.xml \
+    hardware/interfaces/tv/tuner/config/sample_tuner_vts_config.xml:$(TARGET_COPY_OUT_VENDOR)/etc/tuner_vts_config.xml \
 
 # HDMI CEC HAL
 PRODUCT_PACKAGES += android.hardware.tv.cec@1.0-service.mock
diff --git a/tests/hal/hal_implementation_test.cpp b/tests/hal/hal_implementation_test.cpp
index d94d56f..554f01f 100644
--- a/tests/hal/hal_implementation_test.cpp
+++ b/tests/hal/hal_implementation_test.cpp
@@ -17,12 +17,13 @@
 #include <android-base/logging.h>
 #include <android-base/strings.h>
 #include <gtest/gtest.h>
-#include <hidl/metadata.h>
 #include <hidl-util/FQName.h>
+#include <hidl/metadata.h>
 #include <vintf/VintfObject.h>
 
 using namespace android;
 
+// clang-format off
 static const std::set<std::string> kKnownMissingHidl = {
     "android.frameworks.bufferhub@1.0",
     "android.frameworks.cameraservice.device@2.0",
@@ -64,6 +65,7 @@
     "android.hardware.health.storage@1.0", // converted to AIDL, see b/177470478
     "android.hardware.ir@1.0",
     "android.hardware.keymaster@3.0",
+    "android.hardware.keymaster@4.1", // Replaced by KeyMint
     "android.hardware.light@2.0",
     "android.hardware.media.bufferpool@1.0",
     "android.hardware.media.bufferpool@2.0",
@@ -94,215 +96,272 @@
     "android.hidl.base@1.0",
     "android.hidl.memory.token@1.0",
 };
+// clang-format on
 
-static const std::set<std::string> kKnownMissingAidl = {
+struct VersionedAidlPackage {
+  std::string name;
+  size_t version;
+  bool operator<(const VersionedAidlPackage& rhs) const {
+    return (name < rhs.name || (name == rhs.name && version < rhs.version));
+  }
+};
+
+static const std::set<VersionedAidlPackage> kKnownMissingAidl = {
     // types-only packages, which never expect a default implementation
-    "android.hardware.common.",
-    "android.hardware.common.fmq.",
-    "android.hardware.graphics.common.",
+    {"android.hardware.common.", 1},
+    {"android.hardware.common.", 2},
+    {"android.hardware.common.fmq.", 1},
+    {"android.hardware.graphics.common.", 1},
+    {"android.hardware.graphics.common.", 2},
 
     // These KeyMaster types are in an AIDL types-only HAL because they're used
     // by the Identity Credential AIDL HAL. Remove this when fully porting
     // KeyMaster to AIDL.
-    "android.hardware.keymaster.",
-
-    // Temporarily disable keymint, secureclock, and shared secret in favor of
-    // keymaster 4.1. This is required for the transition to Keystore 2.0.
-    // Software keymint does not work with Gatekeeper. This can be removed when
-    // the remote keymaster implementation was ported to keymint.
-    // b/182928606
-    "android.hardware.security.keymint.",
-    "android.hardware.security.secureclock.",
-    "android.hardware.security.sharedsecret.",
+    {"android.hardware.keymaster.", 1},
+    {"android.hardware.keymaster.", 2},
+    {"android.hardware.keymaster.", 3},
 
     // These types are only used in Automotive.
-    "android.automotive.computepipe.registry.",
-    "android.automotive.computepipe.runner.",
-    "android.automotive.watchdog.",
-    "android.hardware.automotive.occupant_awareness.",
+    {"android.automotive.computepipe.registry.", 1},
+    {"android.automotive.computepipe.runner.", 1},
+    {"android.automotive.watchdog.", 2},
+    {"android.hardware.automotive.occupant_awareness.", 1},
+
+    // This version needs to be implemented (b/190236358)
+    {"android.hardware.vibrator.", 2},
 };
 
 // AOSP packages which are never considered
 static bool isHidlPackageConsidered(const FQName& name) {
-    static std::vector<std::string> gAospExclude = {
-        // packages not implemented now that we never expect to be implemented
-        "android.hardware.tests",
-        // packages not registered with hwservicemanager, usually sub-interfaces
-        "android.hardware.camera.device",
-    };
-    for (const std::string& package : gAospExclude) {
-        if (name.inPackage(package)) {
-            return false;
-        }
+  static std::vector<std::string> gAospExclude = {
+      // packages not implemented now that we never expect to be implemented
+      "android.hardware.tests",
+      // packages not registered with hwservicemanager, usually sub-interfaces
+      "android.hardware.camera.device",
+  };
+  for (const std::string& package : gAospExclude) {
+    if (name.inPackage(package)) {
+      return false;
     }
-    return true;
+  }
+  return true;
 }
 
 static bool isAospHidlInterface(const FQName& name) {
-    static const std::vector<std::string> kAospPackages = {
-        "android.hidl",
-        "android.hardware",
-        "android.frameworks",
-        "android.system",
-    };
-    for (const std::string& package : kAospPackages) {
-        if (name.inPackage(package)) {
-            return true;
-        }
+  static const std::vector<std::string> kAospPackages = {
+      "android.hidl",
+      "android.hardware",
+      "android.frameworks",
+      "android.system",
+  };
+  for (const std::string& package : kAospPackages) {
+    if (name.inPackage(package)) {
+      return true;
     }
-    return false;
+  }
+  return false;
 }
 
 static std::set<FQName> allTreeHidlInterfaces() {
-    std::set<FQName> ret;
-    for (const auto& iface : HidlInterfaceMetadata::all()) {
-        FQName f;
-        CHECK(f.setTo(iface.name)) << iface.name;
-        ret.insert(f);
-    }
-    return ret;
+  std::set<FQName> ret;
+  for (const auto& iface : HidlInterfaceMetadata::all()) {
+    FQName f;
+    CHECK(f.setTo(iface.name)) << iface.name;
+    ret.insert(f);
+  }
+  return ret;
 }
 
 static std::set<FQName> allHidlManifestInterfaces() {
-    std::set<FQName> ret;
-    auto setInserter = [&] (const vintf::ManifestInstance& i) -> bool {
-        if (i.format() != vintf::HalFormat::HIDL) {
-            return true;  // continue
-        }
-        ret.insert(i.getFqInstance().getFqName());
-        return true;  // continue
-    };
-    vintf::VintfObject::GetDeviceHalManifest()->forEachInstance(setInserter);
-    vintf::VintfObject::GetFrameworkHalManifest()->forEachInstance(setInserter);
-    return ret;
+  std::set<FQName> ret;
+  auto setInserter = [&](const vintf::ManifestInstance& i) -> bool {
+    if (i.format() != vintf::HalFormat::HIDL) {
+      return true;  // continue
+    }
+    ret.insert(i.getFqInstance().getFqName());
+    return true;  // continue
+  };
+  vintf::VintfObject::GetDeviceHalManifest()->forEachInstance(setInserter);
+  vintf::VintfObject::GetFrameworkHalManifest()->forEachInstance(setInserter);
+  return ret;
 }
 
 static bool isAospAidlInterface(const std::string& name) {
-    return base::StartsWith(name, "android.") &&
-        !base::StartsWith(name, "android.hardware.tests.") &&
-        !base::StartsWith(name, "android.aidl.tests");
+  return base::StartsWith(name, "android.") &&
+         !base::StartsWith(name, "android.hardware.tests.") &&
+         !base::StartsWith(name, "android.aidl.tests");
 }
 
-static std::set<std::string> allAidlManifestInterfaces() {
-    std::set<std::string> ret;
-    auto setInserter = [&] (const vintf::ManifestInstance& i) -> bool {
-        if (i.format() != vintf::HalFormat::AIDL) {
-            return true;  // continue
-        }
-        ret.insert(i.package() + "." + i.interface());
-        return true;  // continue
-    };
-    vintf::VintfObject::GetDeviceHalManifest()->forEachInstance(setInserter);
-    vintf::VintfObject::GetFrameworkHalManifest()->forEachInstance(setInserter);
-    return ret;
+static std::set<VersionedAidlPackage> allAidlManifestInterfaces() {
+  std::set<VersionedAidlPackage> ret;
+  auto setInserter = [&](const vintf::ManifestInstance& i) -> bool {
+    if (i.format() != vintf::HalFormat::AIDL) {
+      return true;  // continue
+    }
+    ret.insert({i.package() + "." + i.interface(), i.version().minorVer});
+    return true;  // continue
+  };
+  vintf::VintfObject::GetDeviceHalManifest()->forEachInstance(setInserter);
+  vintf::VintfObject::GetFrameworkHalManifest()->forEachInstance(setInserter);
+  return ret;
 }
 
 TEST(Hal, AllHidlInterfacesAreInAosp) {
-    for (const FQName& name : allHidlManifestInterfaces()) {
-        EXPECT_TRUE(isAospHidlInterface(name)) << name.string();
-    }
+  for (const FQName& name : allHidlManifestInterfaces()) {
+    EXPECT_TRUE(isAospHidlInterface(name))
+        << "This device should only have AOSP interfaces, not: "
+        << name.string();
+  }
 }
 
 TEST(Hal, HidlInterfacesImplemented) {
-    // instances -> major version -> minor versions
-    std::map<std::string, std::map<size_t, std::set<size_t>>> unimplemented;
+  // instances -> major version -> minor versions
+  std::map<std::string, std::map<size_t, std::set<size_t>>> unimplemented;
 
-    for (const FQName& f : allTreeHidlInterfaces()) {
-        if (!isAospHidlInterface(f)) continue;
-        if (!isHidlPackageConsidered(f)) continue;
+  for (const FQName& f : allTreeHidlInterfaces()) {
+    if (!isAospHidlInterface(f)) continue;
+    if (!isHidlPackageConsidered(f)) continue;
 
-        unimplemented[f.package()][f.getPackageMajorVersion()].insert(f.getPackageMinorVersion());
+    unimplemented[f.package()][f.getPackageMajorVersion()].insert(
+        f.getPackageMinorVersion());
+  }
+
+  // we'll be removing items from this which we know are missing
+  // in order to be left with those elements which we thought we
+  // knew were missing but are actually present
+  std::set<std::string> thoughtMissing = kKnownMissingHidl;
+
+  for (const FQName& f : allHidlManifestInterfaces()) {
+    if (thoughtMissing.erase(f.getPackageAndVersion().string()) > 0) {
+      ADD_FAILURE() << "Instance in missing list, but available: "
+                    << f.string();
     }
 
-    // we'll be removing items from this which we know are missing
-    // in order to be left with those elements which we thought we
-    // knew were missing but are actually present
-    std::set<std::string> thoughtMissing = kKnownMissingHidl;
+    std::set<size_t>& minors =
+        unimplemented[f.package()][f.getPackageMajorVersion()];
+    size_t minor = f.getPackageMinorVersion();
 
-    for (const FQName& f : allHidlManifestInterfaces()) {
-        if (thoughtMissing.erase(f.getPackageAndVersion().string()) > 0) {
-             ADD_FAILURE() << "Instance in missing list, but available: " << f.string();
-        }
+    auto it = minors.find(minor);
+    if (it == minors.end()) continue;
 
-        std::set<size_t>& minors = unimplemented[f.package()][f.getPackageMajorVersion()];
-        size_t minor = f.getPackageMinorVersion();
+    // if 1.2 is implemented, also considere 1.0, 1.1 implemented
+    minors.erase(minors.begin(), std::next(it));
+  }
 
-        auto it = minors.find(minor);
-        if (it == minors.end()) continue;
+  for (const auto& [package, minorsPerMajor] : unimplemented) {
+    for (const auto& [major, minors] : minorsPerMajor) {
+      if (minors.empty()) continue;
 
-        // if 1.2 is implemented, also considere 1.0, 1.1 implemented
-        minors.erase(minors.begin(), std::next(it));
+      size_t maxMinor = *minors.rbegin();
+
+      FQName missing;
+      ASSERT_TRUE(missing.setTo(package, major, maxMinor));
+
+      if (thoughtMissing.erase(missing.string()) > 0) continue;
+
+      ADD_FAILURE() << "Missing implementation from " << missing.string();
     }
+  }
 
-    for (const auto& [package, minorsPerMajor] : unimplemented) {
-        for (const auto& [major, minors] : minorsPerMajor) {
-            if (minors.empty()) continue;
-
-            size_t maxMinor = *minors.rbegin();
-
-            FQName missing;
-            ASSERT_TRUE(missing.setTo(package, major, maxMinor));
-
-            if (thoughtMissing.erase(missing.string()) > 0) continue;
-
-            ADD_FAILURE() << "Missing implementation from " << missing.string();
-        }
-    }
-
-    for (const std::string& missing : thoughtMissing) {
-        ADD_FAILURE() << "Instance in missing list and cannot find it anywhere: " << missing
-                  << " (multiple versions in missing list?)";
-    }
+  for (const std::string& missing : thoughtMissing) {
+    ADD_FAILURE() << "Instance in missing list and cannot find it anywhere: "
+                  << missing << " (multiple versions in missing list?)";
+  }
 }
 
 TEST(Hal, AllAidlInterfacesAreInAosp) {
-    for (const std::string& name : allAidlManifestInterfaces()) {
-        EXPECT_TRUE(isAospAidlInterface(name)) << name;
-    }
+  for (const auto& package : allAidlManifestInterfaces()) {
+    EXPECT_TRUE(isAospAidlInterface(package.name))
+        << "This device should only have AOSP interfaces, not: "
+        << package.name;
+  }
 }
 
 // android.hardware.foo.IFoo -> android.hardware.foo.
 std::string getAidlPackage(const std::string& aidlType) {
-    size_t lastDot = aidlType.rfind('.');
-    CHECK(lastDot != std::string::npos);
-    return aidlType.substr(0, lastDot + 1);
+  size_t lastDot = aidlType.rfind('.');
+  CHECK(lastDot != std::string::npos);
+  return aidlType.substr(0, lastDot + 1);
 }
 
+struct AidlPackageCheck {
+  bool hasRegistration;
+  bool knownMissing;
+};
+
 TEST(Hal, AidlInterfacesImplemented) {
-    std::set<std::string> manifest = allAidlManifestInterfaces();
-    std::set<std::string> thoughtMissing = kKnownMissingAidl;
+  std::set<VersionedAidlPackage> manifest = allAidlManifestInterfaces();
+  std::set<VersionedAidlPackage> thoughtMissing = kKnownMissingAidl;
 
-    for (const auto& iface : AidlInterfaceMetadata::all()) {
-        ASSERT_FALSE(iface.types.empty()) << iface.name;  // sanity
-        if (std::none_of(iface.types.begin(), iface.types.end(), isAospAidlInterface)) continue;
-        if (iface.stability != "vintf") continue;
+  for (const auto& treePackage : AidlInterfaceMetadata::all()) {
+    ASSERT_FALSE(treePackage.types.empty()) << treePackage.name;
+    if (std::none_of(treePackage.types.begin(), treePackage.types.end(),
+                     isAospAidlInterface))
+      continue;
+    if (treePackage.stability != "vintf") continue;
 
-        bool hasRegistration = false;
-        bool knownMissing = false;
-        for (const std::string& type : iface.types) {
-            if (manifest.erase(type) > 0) hasRegistration = true;
-            if (thoughtMissing.erase(getAidlPackage(type)) > 0) knownMissing = true;
+    // expect versions from 1 to latest version. If the package has development
+    // the latest version is the latest known version + 1. Each of these need
+    // to be checked for registration and knownMissing.
+    std::map<size_t, AidlPackageCheck> expectedVersions;
+    for (const auto version : treePackage.versions) {
+      expectedVersions[version] = {false, false};
+    }
+    if (treePackage.has_development) {
+      size_t version =
+          treePackage.versions.empty() ? 1 : *treePackage.versions.rbegin() + 1;
+      expectedVersions[version] = {false, false};
+    }
+
+    // Check all types and versions defined by the package for registration.
+    // The package version is considered registered if any of those types are
+    // present in the manifest with the same version.
+    // The package version is considered known missing if it is found in
+    // thoughtMissing.
+    bool latestRegistered = false;
+    for (const std::string& type : treePackage.types) {
+      for (auto& [version, check] : expectedVersions) {
+        if (manifest.erase({type, version}) > 0) {
+          if (version == expectedVersions.rbegin()->first) {
+            latestRegistered = true;
+          }
+          check.hasRegistration = true;
+        }
+        if (thoughtMissing.erase({getAidlPackage(type), version}) > 0)
+          check.knownMissing = true;
+      }
+    }
+
+    if (!latestRegistered && !expectedVersions.rbegin()->second.knownMissing) {
+      ADD_FAILURE() << "The latest version ("
+                    << expectedVersions.rbegin()->first
+                    << ") of the package is not implemented: "
+                    << treePackage.name
+                    << " which declares the following types:\n    "
+                    << base::Join(treePackage.types, "\n    ");
+    }
+
+    for (const auto& [version, check] : expectedVersions) {
+      if (check.knownMissing) {
+        if (check.hasRegistration) {
+          ADD_FAILURE() << "Package in missing list, but available: "
+                        << treePackage.name << " V" << version
+                        << " which declares the following types:\n    "
+                        << base::Join(treePackage.types, "\n    ");
         }
 
-        if (knownMissing) {
-            if (hasRegistration) {
-                ADD_FAILURE() << "Interface in missing list, but available: " << iface.name
-                          << " which declares the following types:\n    "
-                          << base::Join(iface.types, "\n    ");
-            }
-
-            continue;
-        }
-
-        EXPECT_TRUE(hasRegistration) << iface.name << " which declares the following types:\n    "
-            << base::Join(iface.types, "\n    ");
+        continue;
+      }
     }
+  }
 
-    for (const std::string& iface : thoughtMissing) {
-        ADD_FAILURE() << "Interface in manifest list and cannot find it anywhere: " << iface;
-    }
+  for (const auto& package : thoughtMissing) {
+    ADD_FAILURE() << "Interface in missing list and cannot find it anywhere: "
+                  << package.name << " V" << package.version;
+  }
 
-    for (const std::string& iface : manifest) {
-        ADD_FAILURE() << "Can't find manifest entry in tree: " << iface;
-    }
+  for (const auto& package : manifest) {
+    ADD_FAILURE() << "Can't find manifest entry in tree: " << package.name
+                  << " version: " << package.version;
+  }
 }
diff --git a/tests/powerwash/src/com/android/cuttlefish/tests/PowerwashTest.java b/tests/powerwash/src/com/android/cuttlefish/tests/PowerwashTest.java
index 84fa852..42d9dda 100644
--- a/tests/powerwash/src/com/android/cuttlefish/tests/PowerwashTest.java
+++ b/tests/powerwash/src/com/android/cuttlefish/tests/PowerwashTest.java
@@ -15,15 +15,19 @@
  */
 package com.android.cuttlefish.tests;
 
+import static org.junit.Assert.assertTrue;
+
 import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
+import com.android.tradefed.device.internal.DeviceResetHandler;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
-import java.io.File;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
+
 /**
  * Test powerwash function.
  *
@@ -48,14 +52,18 @@
         if (file == null) {
             Assert.fail("Setup failed: tmp file failed to persist after device reboot.");
         }
-
+        boolean success = false;
         if (getDevice() instanceof RemoteAndroidVirtualDevice) {
-            ((RemoteAndroidVirtualDevice) getDevice()).powerwashGce();
+            success = ((RemoteAndroidVirtualDevice) getDevice()).powerwashGce();
         } else {
-            Assert.fail("This test only supports running in test lab setup.");
+            // We don't usually expect tests to use our feature server, but in this case we are
+            // validating the feature itself so it's fine
+            DeviceResetHandler handler = new DeviceResetHandler(getInvocationContext());
+            success = handler.resetDevice(getDevice());
         }
+        assertTrue("Powerwash reset failed", success);
 
-        // Verify that the device is back online and pre-xisting file is gone.
+        // Verify that the device is back online and pre-existing file is gone.
         file = getDevice().pullFile(tmpFile);
         if (file != null) {
             Assert.fail("Powerwash failed: pre-existing file still exists.");
diff --git a/tests/ril/src/com/android/cuttlefish/ril/tests/RilE2eTests.java b/tests/ril/src/com/android/cuttlefish/ril/tests/RilE2eTests.java
index 3c5db3f..534f676 100644
--- a/tests/ril/src/com/android/cuttlefish/ril/tests/RilE2eTests.java
+++ b/tests/ril/src/com/android/cuttlefish/ril/tests/RilE2eTests.java
@@ -35,7 +35,6 @@
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -141,8 +140,6 @@
         Assert.assertSame(TelephonyManager.DATA_CONNECTED, mTeleManager.getDataState());
     }
 
-    // See b/74256305
-    @Ignore
     @Test
     public void testSignalLevels() throws Exception {
         CellInfoGsm cellinfogsm = (CellInfoGsm)mTeleManager.getAllCellInfo().get(0);
diff --git a/tools/latest_fetch_cvd.sh b/tools/latest_fetch_cvd.sh
new file mode 100755
index 0000000..d853786
--- /dev/null
+++ b/tools/latest_fetch_cvd.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+LATEST_BUILD_ID=`curl "https://www.googleapis.com/android/internal/build/v3/builds?branch=aosp-master&buildAttemptStatus=complete&buildType=submitted&maxResults=1&successful=true&target=aosp_cf_x86_64_phone-userdebug" 2>/dev/null | \
+  python3 -c "import sys, json; print(json.load(sys.stdin)['builds'][0]['buildId'])"`
+LATEST_BUILD_URL=`curl "https://www.googleapis.com/android/internal/build/v3/builds/$LATEST_BUILD_ID/aosp_cf_x86_64_phone-userdebug/attempts/latest/artifacts/fetch_cvd/url" 2>/dev/null | \
+  python3 -c "import sys, json; print(json.load(sys.stdin)['signedUrl'])"`
+
+DOWNLOAD_TARGET=`mktemp`
+
+curl "${LATEST_BUILD_URL}" -o $DOWNLOAD_TARGET
+
+chmod +x $DOWNLOAD_TARGET
+
+exec $DOWNLOAD_TARGET $@
diff --git a/vsoc_arm64/BoardConfig.mk b/vsoc_arm64/BoardConfig.mk
index 048d8cd..bc016a8 100644
--- a/vsoc_arm64/BoardConfig.mk
+++ b/vsoc_arm64/BoardConfig.mk
@@ -20,9 +20,6 @@
 
 -include device/google/cuttlefish/shared/BoardConfig.mk
 
-# GKI_VER is defined in kernel.mk, if not defined in the environment variable.
-BOARD_KERNEL_MODULE_INTERFACE_VERSIONS := $(GKI_VER)-android12-0
-
 TARGET_BOARD_PLATFORM := vsoc_arm64
 TARGET_ARCH := arm64
 TARGET_ARCH_VARIANT := armv8-a
@@ -36,7 +33,7 @@
 TARGET_TRANSLATE_2ND_ARCH := false
 
 ifeq ($(BOARD_VENDOR_RAMDISK_KERNEL_MODULES),)
-    BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/$(GKI_VER)/arm64/*.ko)
+    BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/$(TARGET_KERNEL_USE)/arm64/*.ko)
 endif
 
 HOST_CROSS_OS := linux_bionic
diff --git a/vsoc_arm64/bootloader.mk b/vsoc_arm64/bootloader.mk
index a5aea94..ce29443 100644
--- a/vsoc_arm64/bootloader.mk
+++ b/vsoc_arm64/bootloader.mk
@@ -18,5 +18,3 @@
 # FIXME: Copying the QEMU bootloader for now, but this should be updated..
 BOARD_PREBUILT_BOOTLOADER := \
     device/google/cuttlefish_prebuilts/bootloader/crosvm_aarch64/u-boot.bin
-PRODUCT_COPY_FILES += \
-    device/google/cuttlefish_prebuilts/bootloader/qemu_aarch64/u-boot.bin:bootloader.qemu
diff --git a/vsoc_arm64/kernel.mk b/vsoc_arm64/kernel.mk
index bcc15cf..c27f7cb 100644
--- a/vsoc_arm64/kernel.mk
+++ b/vsoc_arm64/kernel.mk
@@ -13,6 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-GKI_VER ?= 5.10
+TARGET_KERNEL_USE ?= 5.10
 
-PRODUCT_COPY_FILES += kernel/prebuilts/$(GKI_VER)/arm64/kernel-$(GKI_VER):kernel
+PRODUCT_COPY_FILES += kernel/prebuilts/$(TARGET_KERNEL_USE)/arm64/kernel-$(TARGET_KERNEL_USE):kernel
diff --git a/vsoc_arm64_only/phone/aosp_cf.mk b/vsoc_arm64_only/phone/aosp_cf.mk
index 6de27b6..5bcfc7b 100644
--- a/vsoc_arm64_only/phone/aosp_cf.mk
+++ b/vsoc_arm64_only/phone/aosp_cf.mk
@@ -55,7 +55,7 @@
 PRODUCT_NAME := aosp_cf_arm64_only_phone
 PRODUCT_DEVICE := vsoc_arm64_only
 PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish arm64 phone (64-bit only)
+PRODUCT_MODEL := Cuttlefish arm64 phone 64-bit only
 
 PRODUCT_VENDOR_PROPERTIES += \
     ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
diff --git a/vsoc_arm_only/bootloader.mk b/vsoc_arm_only/bootloader.mk
index 93de14e..959cd61 100644
--- a/vsoc_arm_only/bootloader.mk
+++ b/vsoc_arm_only/bootloader.mk
@@ -18,5 +18,3 @@
 # FIXME: Copying the QEMU bootloader for now, but this should be updated..
 BOARD_PREBUILT_BOOTLOADER := \
     device/google/cuttlefish_prebuilts/bootloader/qemu_arm/u-boot.bin
-PRODUCT_COPY_FILES += \
-    device/google/cuttlefish_prebuilts/bootloader/qemu_arm/u-boot.bin:bootloader.qemu
diff --git a/vsoc_arm_only/phone/aosp_cf.mk b/vsoc_arm_only/phone/aosp_cf.mk
index e0875bf..2643c90 100644
--- a/vsoc_arm_only/phone/aosp_cf.mk
+++ b/vsoc_arm_only/phone/aosp_cf.mk
@@ -40,6 +40,7 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/go_defaults_512.mk)
 PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST += \
     system/apex/com.android.tethering.inprocess.apex \
+    system/apex/com.android.tethering.inprocess.capex \
     system/app/PlatformCaptivePortalLogin/PlatformCaptivePortalLogin.apk \
     system/priv-app/CellBroadcastServiceModulePlatform/CellBroadcastServiceModulePlatform.apk \
     system/priv-app/InProcessNetworkStack/InProcessNetworkStack.apk \
@@ -63,7 +64,7 @@
 PRODUCT_NAME := aosp_cf_arm_only_phone
 PRODUCT_DEVICE := vsoc_arm_only
 PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish arm phone (32-bit only)
+PRODUCT_MODEL := Cuttlefish arm phone 32-bit only
 
 PRODUCT_VENDOR_PROPERTIES += \
     ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
diff --git a/vsoc_x86/auto/audio_policy_configuration.xml b/vsoc_x86/auto/audio_policy_configuration.xml
new file mode 100644
index 0000000..93d4130
--- /dev/null
+++ b/vsoc_x86/auto/audio_policy_configuration.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<!--
+  Overlay resources to configure car service based on each OEM's preference.
+  See also packages/services/Car/service/res/values/config.xml
+-->
+<audioPolicyConfiguration version="1.0" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <!-- Global configuration Decalaration -->
+    <globalConfiguration speaker_drc_enabled="true"/>
+
+    <module name="primary" halVersion="2.0">
+        <attachedDevices>
+            <item>Speaker</item>
+            <item>Built-In Mic</item>
+        </attachedDevices>
+        <defaultOutputDevice>Speaker</defaultOutputDevice>
+        <mixPorts>
+            <mixPort name="primary output" role="source" flags="AUDIO_OUTPUT_FLAG_PRIMARY">
+                <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                    samplingRates="44100" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+            </mixPort>
+            <mixPort name="primary input" role="sink">
+                <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                    samplingRates="8000,16000" channelMasks="AUDIO_CHANNEL_IN_MONO"/>
+            </mixPort>
+        </mixPorts>
+        <devicePorts>
+            <devicePort tagName="Speaker" role="sink" type="AUDIO_DEVICE_OUT_BUS"
+                address="Speaker">
+                <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                    samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+                <gains>
+                    <gain name="" mode="AUDIO_GAIN_MODE_JOINT"
+                        minValueMB="-3200" maxValueMB="600" defaultValueMB="0" stepValueMB="100"/>
+                </gains>
+            </devicePort>
+
+            <devicePort tagName="Built-In Mic" type="AUDIO_DEVICE_IN_BUILTIN_MIC" role="source">
+            </devicePort>
+        </devicePorts>
+        <routes>
+            <route type="mix" sink="Speaker"
+                sources="primary output"/>
+            <route type="mix" sink="primary input"
+                sources="Built-In Mic"/>
+        </routes>
+    </module>
+
+    <xi:include href="audio_policy_volumes.xml"/>
+    <xi:include href="default_volume_tables.xml"/>
+
+    <!-- End of Volume section -->
+    <!-- End of Modules section -->
+
+</audioPolicyConfiguration>
\ No newline at end of file
diff --git a/vsoc_x86/auto/car_audio_configuration.xml b/vsoc_x86/auto/car_audio_configuration.xml
new file mode 100644
index 0000000..53ca217
--- /dev/null
+++ b/vsoc_x86/auto/car_audio_configuration.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+  Defines the audio configuration in a car, including
+    - Audio zones
+    - Context to audio bus mappings
+    - Volume groups
+  in the car environment.
+-->
+<carAudioConfiguration version="2">
+    <zones>
+        <zone name="primary zone" isPrimary="true">
+            <volumeGroups>
+                <group>
+                    <device address="Speaker">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="notification"/>
+                        <context context="system_sound"/>
+                        <context context="emergency"/>
+                        <context context="safety"/>
+                        <context context="vehicle_status"/>
+                        <context context="announcement"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/vsoc_x86/auto/overlay/frameworks/base/core/res/res/values/config.xml b/vsoc_x86/auto/overlay/frameworks/base/core/res/res/values/config.xml
new file mode 100644
index 0000000..ca5f91e
--- /dev/null
+++ b/vsoc_x86/auto/overlay/frameworks/base/core/res/res/values/config.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<!--
+  Overlay resources to configure car service based on each OEM's preference.
+  See also packages/services/Car/service/res/values/config.xml
+-->
+<resources>
+    <!-- Car uses hardware amplifier for volume. -->
+    <bool name="config_useFixedVolume">true</bool>
+    <!--
+      Handle volume keys directly in CarAudioService without passing them to the foreground app
+    -->
+    <bool name="config_handleVolumeKeysInWindowManager">true</bool>
+</resources>
diff --git a/vsoc_x86/auto/overlay/packages/services/Car/service/res/values/config.xml b/vsoc_x86/auto/overlay/packages/services/Car/service/res/values/config.xml
new file mode 100644
index 0000000..f1585a2
--- /dev/null
+++ b/vsoc_x86/auto/overlay/packages/services/Car/service/res/values/config.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<!--
+  Overlay resources to configure car service based on each OEM's preference.
+  See also packages/services/Car/service/res/values/config.xml
+-->
+<resources>
+    <bool name="audioUseDynamicRouting">true</bool>
+    <!--  Configuration to enable muting of individual volume groups. If this is set to
+          false, muting of individual volume groups is disabled, instead muting will toggle master
+          mute. If this is set to true, car volume group muting is enabled and each individual
+          volume group can be muted separately. -->
+    <bool name="audioUseCarVolumeGroupMuting">false</bool>
+    <!--  Configuration to enable IAudioControl#onDevicesToDuckChange API to inform HAL when to
+      duck. If this is set to true, the API will receive signals indicating which output devices
+      to duck as well as what usages are currently holding focus. If set to false, the API will
+      not be called. -->
+    <bool name="audioUseHalDuckingSignals">false</bool>
+</resources>
diff --git a/vsoc_x86/go_512_phone/device.mk b/vsoc_x86/go_512_phone/device.mk
index 2df9023..1a34001 100644
--- a/vsoc_x86/go_512_phone/device.mk
+++ b/vsoc_x86/go_512_phone/device.mk
@@ -16,6 +16,7 @@
 
 $(call inherit-product, device/google/cuttlefish/shared/go_512/device.mk)
 $(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
 
 PRODUCT_NAME := aosp_cf_x86_go_512_phone
 PRODUCT_DEVICE := vsoc_x86
diff --git a/vsoc_x86/go_phone/device.mk b/vsoc_x86/go_phone/device.mk
index 8b2a8f3..6fe021b 100644
--- a/vsoc_x86/go_phone/device.mk
+++ b/vsoc_x86/go_phone/device.mk
@@ -16,6 +16,7 @@
 
 $(call inherit-product, device/google/cuttlefish/shared/go/device.mk)
 $(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
 
 PRODUCT_NAME := aosp_cf_x86_go_phone
 PRODUCT_DEVICE := vsoc_x86
diff --git a/vsoc_x86_64/BoardConfig.mk b/vsoc_x86_64/BoardConfig.mk
index 7e3d927..52dde5c 100644
--- a/vsoc_x86_64/BoardConfig.mk
+++ b/vsoc_x86_64/BoardConfig.mk
@@ -20,9 +20,6 @@
 
 -include device/google/cuttlefish/shared/BoardConfig.mk
 
-# GKI_VER is defined in kernel.mk, if not defined in the environment variable.
-BOARD_KERNEL_MODULE_INTERFACE_VERSIONS := $(GKI_VER)-android12-0
-
 TARGET_BOARD_PLATFORM := vsoc_x86_64
 TARGET_ARCH := x86_64
 TARGET_ARCH_VARIANT := silvermont
@@ -46,5 +43,5 @@
 BUILD_BROKEN_DUP_RULES := true
 
 ifeq ($(BOARD_VENDOR_RAMDISK_KERNEL_MODULES),)
-    BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/$(GKI_VER)/x86-64/*.ko)
+    BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/$(TARGET_KERNEL_USE)/x86-64/*.ko)
 endif
diff --git a/vsoc_x86_64/bootloader.mk b/vsoc_x86_64/bootloader.mk
index b0d8c5d..6294ca7 100644
--- a/vsoc_x86_64/bootloader.mk
+++ b/vsoc_x86_64/bootloader.mk
@@ -17,5 +17,3 @@
 TARGET_NO_BOOTLOADER := false
 BOARD_PREBUILT_BOOTLOADER := \
     device/google/cuttlefish_prebuilts/bootloader/crosvm_x86_64/u-boot.rom
-PRODUCT_COPY_FILES += \
-    device/google/cuttlefish_prebuilts/bootloader/qemu_x86_64/u-boot.rom:bootloader.qemu
diff --git a/vsoc_x86_64/kernel.mk b/vsoc_x86_64/kernel.mk
index dccbeda..5dded66 100644
--- a/vsoc_x86_64/kernel.mk
+++ b/vsoc_x86_64/kernel.mk
@@ -13,6 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-GKI_VER ?= 5.10
+TARGET_KERNEL_USE ?= 5.10
 
-PRODUCT_COPY_FILES += kernel/prebuilts/$(GKI_VER)/x86_64/kernel-$(GKI_VER):kernel
+PRODUCT_COPY_FILES += kernel/prebuilts/$(TARGET_KERNEL_USE)/x86_64/kernel-$(TARGET_KERNEL_USE):kernel
diff --git a/vsoc_x86_64_only/phone/aosp_cf.mk b/vsoc_x86_64_only/phone/aosp_cf.mk
index 9596d5a..0e8757d 100644
--- a/vsoc_x86_64_only/phone/aosp_cf.mk
+++ b/vsoc_x86_64_only/phone/aosp_cf.mk
@@ -55,7 +55,7 @@
 PRODUCT_NAME := aosp_cf_x86_64_only_phone
 PRODUCT_DEVICE := vsoc_x86_64_only
 PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86_64 phone (64-bit only)
+PRODUCT_MODEL := Cuttlefish x86_64 phone 64-bit only
 
 PRODUCT_VENDOR_PROPERTIES += \
     ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
diff --git a/vsoc_x86_noapex/aosp_cf_noapex.mk b/vsoc_x86_noapex/aosp_cf_noapex.mk
index aa5d3b2..dd5d642 100644
--- a/vsoc_x86_noapex/aosp_cf_noapex.mk
+++ b/vsoc_x86_noapex/aosp_cf_noapex.mk
@@ -25,7 +25,3 @@
 PRODUCT_DEVICE := vsoc_x86_noapex
 PRODUCT_MANUFACTURER := Google
 PRODUCT_MODEL := Cuttlefish x86 phone without APEX support
-
-PRODUCT_VENDOR_PROPERTIES += \
-    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
-    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_only/BoardConfig.mk b/vsoc_x86_only/BoardConfig.mk
index ec97f2d..8703a10 100644
--- a/vsoc_x86_only/BoardConfig.mk
+++ b/vsoc_x86_only/BoardConfig.mk
@@ -20,12 +20,9 @@
 
 -include device/google/cuttlefish/shared/BoardConfig.mk
 
-# GKI_VER is defined in kernel.mk, if not defined in the environment variable.
-BOARD_KERNEL_MODULE_INTERFACE_VERSIONS := $(GKI_VER)-android12-0
-
 TARGET_BOARD_PLATFORM := vsoc_x86
 TARGET_ARCH := x86
 TARGET_ARCH_VARIANT := x86
 TARGET_CPU_ABI := x86
 
-BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard device/google/cuttlefish_prebuilts/kernel/$(GKI_VER)-i686/*.ko)
+BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard device/google/cuttlefish_prebuilts/kernel/$(TARGET_KERNEL_USE)-i686/*.ko)
diff --git a/vsoc_x86_only/kernel.mk b/vsoc_x86_only/kernel.mk
index e6d4924..23cf086 100644
--- a/vsoc_x86_only/kernel.mk
+++ b/vsoc_x86_only/kernel.mk
@@ -13,6 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-GKI_VER ?= 5.10
+TARGET_KERNEL_USE ?= 5.10
 
-PRODUCT_COPY_FILES += device/google/cuttlefish_prebuilts/kernel/$(GKI_VER)-i686/kernel-$(GKI_VER):kernel
+PRODUCT_COPY_FILES += device/google/cuttlefish_prebuilts/kernel/$(TARGET_KERNEL_USE)-i686/kernel-$(TARGET_KERNEL_USE):kernel
diff --git a/vsoc_x86_only/phone/aosp_cf.mk b/vsoc_x86_only/phone/aosp_cf.mk
index 17324d4..1aacd16 100644
--- a/vsoc_x86_only/phone/aosp_cf.mk
+++ b/vsoc_x86_only/phone/aosp_cf.mk
@@ -53,7 +53,7 @@
 PRODUCT_NAME := aosp_cf_x86_only_phone
 PRODUCT_DEVICE := vsoc_x86_only
 PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86 phone (32-bit kernel)
+PRODUCT_MODEL := Cuttlefish x86 phone 32-bit kernel
 
 PRODUCT_VENDOR_PROPERTIES += \
     ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \