Merge "Use the right openssl header" into main
diff --git a/Android.bp b/Android.bp
index e9390b9..133c182 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,7 +97,8 @@
     name: "cf_cc_defaults",
     module_type: "cc_defaults",
     config_namespace: "cvdhost",
-    bool_variables: ["enforce_mac80211_hwsim", "vhost_user_vsock_by_default"],
+    bool_variables: ["enforce_mac80211_hwsim"],
+    value_variables: ["default_userdata_fs_type"],
     properties: ["cflags"],
 }
 
@@ -112,10 +113,12 @@
                 cflags: [],
             }
         },
-        vhost_user_vsock_by_default: {
-            cflags: ["-DVHOST_USER_VSOCK_BY_DEFAULT=true"],
+        // TARGET_USERDATAIMAGE_FILE_SYSTEM_TYPE sets this from BoardConfig.mk
+        // The only user is the page agnostic cf target
+        default_userdata_fs_type: {
+            cflags: ["-DUSERDATA_FILE_SYSTEM_TYPE=\"%s\""],
             conditions_default: {
-                cflags: [],
+                cflags: ["-DUSERDATA_FILE_SYSTEM_TYPE=\"f2fs\""],
             }
         }
     }
diff --git a/Android.mk b/Android.mk
index eca8725..fd9e410 100644
--- a/Android.mk
+++ b/Android.mk
@@ -26,7 +26,6 @@
 $(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,wpa_supplicant_overlay.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
 $(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,wpa_supplicant.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
 $(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,init.cutf_cvm.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
-$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,bt_vhci_forwarder.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
 $(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,fstab.cf.f2fs.hctr2,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
 $(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,fstab.cf.f2fs.cts,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
 $(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,fstab.cf.ext4.hctr2,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
diff --git a/apex/com.google.aosp_cf_phone.rros/Android.bp b/apex/com.google.aosp_cf.rros/Android.bp
similarity index 85%
rename from apex/com.google.aosp_cf_phone.rros/Android.bp
rename to apex/com.google.aosp_cf.rros/Android.bp
index 325e1b3..acb3add 100644
--- a/apex/com.google.aosp_cf_phone.rros/Android.bp
+++ b/apex/com.google.aosp_cf.rros/Android.bp
@@ -17,19 +17,18 @@
 }
 
 apex {
-    name: "com.google.aosp_cf_phone.rros",
+    name: "com.google.aosp_cf.rros",
     manifest: "apex_manifest.json",
     key: "com.google.cf.apex.key",
     certificate: ":com.google.cf.apex.certificate",
     file_contexts: "file_contexts",
-    use_vndk_as_stable: true,
     updatable: false,
-    // Install the apex in /vendor/apex
     soc_specific: true,
+
+    // RROs shared across CF-derived devices
     rros: [
         "cuttlefish_overlay_connectivity",
         "cuttlefish_overlay_frameworks_base_core",
         "cuttlefish_overlay_settings_provider",
-        "cuttlefish_phone_overlay_frameworks_base_core",
     ],
 }
diff --git a/apex/com.google.aosp_cf.rros/apex_manifest.json b/apex/com.google.aosp_cf.rros/apex_manifest.json
new file mode 100644
index 0000000..faf4eef
--- /dev/null
+++ b/apex/com.google.aosp_cf.rros/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.google.aosp_cf.rros",
+  "version": 1
+}
diff --git a/apex/com.google.aosp_cf_slim.rros/file_contexts b/apex/com.google.aosp_cf.rros/file_contexts
similarity index 100%
rename from apex/com.google.aosp_cf_slim.rros/file_contexts
rename to apex/com.google.aosp_cf.rros/file_contexts
diff --git a/apex/com.google.aosp_cf_phone.rros/apex_manifest.json b/apex/com.google.aosp_cf_phone.rros/apex_manifest.json
deleted file mode 100644
index e5ebe27..0000000
--- a/apex/com.google.aosp_cf_phone.rros/apex_manifest.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "name": "com.google.aosp_cf_phone.rros",
-  "version": 1
-}
diff --git a/apex/com.google.aosp_cf_phone.rros/file_contexts b/apex/com.google.aosp_cf_phone.rros/file_contexts
deleted file mode 100644
index cb7fd8d..0000000
--- a/apex/com.google.aosp_cf_phone.rros/file_contexts
+++ /dev/null
@@ -1,2 +0,0 @@
-(/.*)?		u:object_r:vendor_file:s0
-/overlay(/.*)?	u:object_r:vendor_overlay_file:s0
diff --git a/apex/com.google.aosp_cf_slim.rros/Android.bp b/apex/com.google.aosp_cf_slim.rros/Android.bp
deleted file mode 100644
index 1a98d1c..0000000
--- a/apex/com.google.aosp_cf_slim.rros/Android.bp
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2022 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-soong_namespace {
-    imports: [
-        "device/generic/goldfish",
-    ],
-}
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-apex {
-    name: "com.google.aosp_cf_slim.rros",
-    manifest: "apex_manifest.json",
-    key: "com.google.cf.apex.key",
-    certificate: ":com.google.cf.apex.certificate",
-    file_contexts: "file_contexts",
-    use_vndk_as_stable: true,
-    updatable: false,
-    // Install the apex in /vendor/apex
-    soc_specific: true,
-    rros: [
-        "slim_overlay_frameworks_base_core",
-    ],
-}
diff --git a/apex/com.google.aosp_cf_slim.rros/apex_manifest.json b/apex/com.google.aosp_cf_slim.rros/apex_manifest.json
deleted file mode 100644
index 26c7bd5..0000000
--- a/apex/com.google.aosp_cf_slim.rros/apex_manifest.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "name": "com.google.aosp_cf_slim.rros",
-  "version": 1
-}
diff --git a/apex/com.google.cf.bt/Android.bp b/apex/com.google.cf.bt/Android.bp
index 8a20aa6..6fa580c 100644
--- a/apex/com.google.cf.bt/Android.bp
+++ b/apex/com.google.cf.bt/Android.bp
@@ -22,29 +22,60 @@
     installable: false,
 }
 
+prebuilt_etc {
+    name: "android.hardware.bluetooth-service.default.xml",
+    src: ":manifest_android.hardware.bluetooth-service.default.xml",
+    sub_dir: "vintf",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "android.hardware.bluetooth.finder-service.default.xml",
+    src: ":manifest_android.hardware.bluetooth.finder-service.default.xml",
+    sub_dir: "vintf",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "android.hardware.bluetooth.ranging-service.default.xml",
+    src: ":manifest_android.hardware.bluetooth.ranging-service.default.xml",
+    sub_dir: "vintf",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "android.hardware.bluetooth.lmp_event-service.default.xml",
+    src: ":manifest_android.hardware.bluetooth.lmp_event-service.default.xml",
+    sub_dir: "vintf",
+    installable: false,
+}
+
 apex {
     name: "com.google.cf.bt",
     manifest: "manifest.json",
     file_contexts: "file_contexts",
     key: "com.google.cf.apex.key",
     certificate: ":com.google.cf.apex.certificate",
-    use_vndk_as_stable: true,
     updatable: false,
     soc_specific: true,
+
     binaries: [
         "android.hardware.bluetooth-service.default",
         "android.hardware.bluetooth.finder-service.default",
         "android.hardware.bluetooth.ranging-service.default",
+        "android.hardware.bluetooth.lmp_event-service.default",
         "bt_vhci_forwarder",
     ],
     prebuilts: [
+        // permissions
         "android.hardware.bluetooth_le.prebuilt.xml",
         "android.hardware.bluetooth.prebuilt.xml",
+        // vintf
+        "android.hardware.bluetooth-service.default.xml",
+        "android.hardware.bluetooth.finder-service.default.xml",
+        "android.hardware.bluetooth.ranging-service.default.xml",
+        "android.hardware.bluetooth.lmp_event-service.default.xml",
+        // init rc
         "com.google.cf.bt.rc",
     ],
-    vintf_fragments: [
-        ":manifest_android.hardware.bluetooth-service.default.xml",
-        ":manifest_android.hardware.bluetooth.finder-service.default.xml",
-        ":manifest_android.hardware.bluetooth.ranging-service.default.xml",
-        ],
 }
diff --git a/apex/com.google.cf.bt/com.google.cf.bt.rc b/apex/com.google.cf.bt/com.google.cf.bt.rc
index 183f4aa..9401dda 100644
--- a/apex/com.google.cf.bt/com.google.cf.bt.rc
+++ b/apex/com.google.cf.bt/com.google.cf.bt.rc
@@ -23,3 +23,9 @@
     user bluetooth
     group bluetooth net_admin net_bt_admin
     capabilities NET_ADMIN
+
+service bt_lmp_event /apex/com.google.cf.bt/bin/hw/android.hardware.bluetooth.lmp_event-service.default
+    class hal
+    user bluetooth
+    group bluetooth net_admin net_bt_admin
+    capabilities NET_ADMIN
diff --git a/apex/com.google.cf.bt/file_contexts b/apex/com.google.cf.bt/file_contexts
index b5dd759..a3e9dfb 100644
--- a/apex/com.google.cf.bt/file_contexts
+++ b/apex/com.google.cf.bt/file_contexts
@@ -2,5 +2,6 @@
 /bin/hw/android.hardware.bluetooth-service.default            u:object_r:hal_bluetooth_btlinux_exec:s0
 /bin/hw/android.hardware.bluetooth.finder-service.default     u:object_r:hal_bluetooth_btlinux_exec:s0
 /bin/hw/android.hardware.bluetooth.ranging-service.default    u:object_r:hal_bluetooth_btlinux_exec:s0
+/bin/hw/android.hardware.bluetooth.lmp_event-service.default  u:object_r:hal_bluetooth_btlinux_exec:s0
 /bin/bt_vhci_forwarder                                        u:object_r:bt_vhci_forwarder_exec:s0
-/etc/permissions(/.*)?                                        u:object_r:vendor_configs_file:s0
+/etc(/.*)?                                                    u:object_r:vendor_configs_file:s0
diff --git a/apex/com.google.cf.rild/Android.bp b/apex/com.google.cf.rild/Android.bp
index aee3fac..5a256bd 100644
--- a/apex/com.google.cf.rild/Android.bp
+++ b/apex/com.google.cf.rild/Android.bp
@@ -23,8 +23,9 @@
 }
 
 prebuilt_etc {
-    name: "ld.config.txt",
-    src: "ld.config.txt",
+    name: "com.google.cf.rild.xml",
+    src: ":libril-modem-lib-manifests",
+    sub_dir: "vintf",
     installable: false,
 }
 
@@ -34,10 +35,9 @@
     key: "com.google.cf.apex.key",
     certificate: ":com.google.cf.apex.certificate",
     file_contexts: "file_contexts",
-    use_vndk_as_stable: true,
     updatable: false,
-    // Install the apex in /vendor/apex
     soc_specific: true,
+
     binaries: [
         "libcuttlefish-rild",
     ],
@@ -47,11 +47,9 @@
     prebuilts: [
         "android.hardware.telephony.gsm.prebuilt.xml",
         "android.hardware.telephony.ims.prebuilt.xml",
-        "android.hardware.telephony.satellite.prebuilt.xml",
         "com.google.cf.rild.rc",
-        "ld.config.txt",
+        "com.google.cf.rild.xml",
     ],
-    vintf_fragments: [":libril-modem-lib-manifests"],
     overrides: [
         "libril",
         "libreference-ril",
diff --git a/apex/com.google.cf.rild/file_contexts b/apex/com.google.cf.rild/file_contexts
index fc0d328..643f7b9 100644
--- a/apex/com.google.cf.rild/file_contexts
+++ b/apex/com.google.cf.rild/file_contexts
@@ -1,3 +1,3 @@
 (/.*)?                        u:object_r:vendor_file:s0
 /bin/hw/libcuttlefish-rild    u:object_r:libcuttlefish_rild_exec:s0
-/etc/permissions(/.*)?        u:object_r:vendor_configs_file:s0
+/etc(/.*)?                    u:object_r:vendor_configs_file:s0
diff --git a/apex/com.google.cf.rild/ld.config.txt b/apex/com.google.cf.rild/ld.config.txt
deleted file mode 100644
index c088357..0000000
--- a/apex/com.google.cf.rild/ld.config.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-dir.myapex = /apex/com.google.cf.rild/bin
-
-[myapex]
-additional.namespaces = vndk
-namespace.default.search.paths  = /apex/com.google.cf.rild/${LIB}
-# For android.hardware.radio-service.compat
-namespace.vndk.permitted.paths  = /vendor/${LIB}/hw
diff --git a/build/Android.bp b/build/Android.bp
index 38c159c..4644730 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -140,6 +140,7 @@
     "casimir",
     "snapshot_util_cvd",
     "run_cvd",
+    "screen_recording_server",
     "secure_env",
     "sefcontext_compile",
     "cvd_send_sms",
diff --git a/common/libs/utils/proto.h b/common/libs/utils/proto.h
new file mode 100644
index 0000000..77b356c
--- /dev/null
+++ b/common/libs/utils/proto.h
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <type_traits>
+#include <vector>
+
+#include <fmt/core.h>
+#include <google/protobuf/message.h>
+#include <google/protobuf/text_format.h>
+#include <google/protobuf/util/json_util.h>
+
+/* Format proto messages as textproto with {} or {:t} and json with {:j}. */
+template <typename T>
+struct fmt::formatter<
+    T,
+    std::enable_if_t<std::is_base_of_v<google::protobuf::Message, T>, char>> {
+ public:
+  constexpr format_parse_context::iterator parse(format_parse_context& ctx) {
+    auto it = ctx.begin();
+    for (; it != ctx.end() && *it != '}'; it++) {
+      format_ = *it;
+    }
+    return it;
+  }
+  format_context::iterator format(const T& proto, format_context& ctx) const {
+    std::string text;
+    if (format_ == 't') {
+      if (!google::protobuf::TextFormat::PrintToString(proto, &text)) {
+        return fmt::format_to(ctx.out(), "(proto format error");
+      }
+    } else if (format_ == 'j') {
+      auto result = google::protobuf::util::MessageToJsonString(proto, &text);
+      if (!result.ok()) {
+        return fmt::format_to(ctx.out(), "(json error: {})",
+                              result.message().ToString());
+      }
+    } else {
+      return fmt::format_to(ctx.out(), "(unknown format specifier)");
+    }
+    return fmt::format_to(ctx.out(), "{}", text);
+  }
+
+ private:
+  char format_ = 't';
+};
diff --git a/common/libs/utils/result.h b/common/libs/utils/result.h
index 6ad2a42..2aca246 100644
--- a/common/libs/utils/result.h
+++ b/common/libs/utils/result.h
@@ -23,10 +23,9 @@
 #include <utility>
 #include <vector>
 
+#include <android-base/format.h>  // IWYU pragma: export
 #include <android-base/logging.h>
 #include <android-base/result.h>  // IWYU pragma: export
-#include <fmt/core.h>             // IWYU pragma: export
-#include <fmt/format.h>
 
 namespace cuttlefish {
 
@@ -232,8 +231,12 @@
           }
           break;
         case FormatSpecifier::kPrettyFunction:
-          out = fmt::format_to(out, "{}{}{}", kTerminalCyan, pretty_function_,
-                               kTerminalReset);
+          if (color) {
+            out = fmt::format_to(out, "{}{}{}", kTerminalCyan, pretty_function_,
+                                 kTerminalReset);
+          } else {
+            out = fmt::format_to(out, "{}", pretty_function_);
+          }
           break;
         case FormatSpecifier::kShort: {
           auto last_slash = file_.rfind("/");
@@ -290,9 +293,11 @@
   std::stringstream message_;
 };
 
-inline std::string ResultErrorFormat() {
+inline std::string ResultErrorFormat(bool color) {
   auto error_format = getenv("CF_ERROR_FORMAT");
-  std::string fmt_str = error_format == nullptr ? "cns/acLFEm" : error_format;
+  std::string default_error_format = (color ? "cns/acLFEm" : "ns/aLFEm");
+  std::string fmt_str =
+      error_format == nullptr ? default_error_format : error_format;
   if (fmt_str.find("}") != std::string::npos) {
     fmt_str = "v";
   }
@@ -360,12 +365,14 @@
   }
   const std::vector<StackTraceEntry>& Stack() const { return stack_; }
 
-  std::string Message() const { return fmt::format("{:m}", *this); }
+  std::string Message() const {
+    return fmt::format(fmt::runtime("{:m}"), *this);
+  }
 
-  std::string Trace() const { return fmt::format("{:v}", *this); }
+  std::string Trace() const { return fmt::format(fmt::runtime("{:v}"), *this); }
 
-  std::string FormatForEnv() const {
-    return fmt::format(ResultErrorFormat(), *this);
+  std::string FormatForEnv(bool color = (isatty(STDERR_FILENO) == 1)) const {
+    return fmt::format(fmt::runtime(ResultErrorFormat(color)), *this);
   }
 
   template <typename T>
diff --git a/common/libs/utils/tee_logging.cpp b/common/libs/utils/tee_logging.cpp
index bfc2d05..a8cb52e9 100644
--- a/common/libs/utils/tee_logging.cpp
+++ b/common/libs/utils/tee_logging.cpp
@@ -204,7 +204,7 @@
 }
 
 // TODO(schuffelen): Do something less primitive.
-static std::string StripColorCodes(const std::string& str) {
+std::string StripColorCodes(const std::string& str) {
   std::stringstream sstream;
   bool in_color_code = false;
   for (char c : str) {
diff --git a/common/libs/utils/tee_logging.h b/common/libs/utils/tee_logging.h
index cc14907..af86e59 100644
--- a/common/libs/utils/tee_logging.h
+++ b/common/libs/utils/tee_logging.h
@@ -70,4 +70,6 @@
                               const std::string& log_prefix = "",
                               MetadataLevel stderr_level = MetadataLevel::ONLY_MESSAGE);
 
+std::string StripColorCodes(const std::string& str);
+
 } // namespace cuttlefish
diff --git a/guest/commands/bt_vhci_forwarder/hci/h4_packetizer.cc b/guest/commands/bt_vhci_forwarder/hci/h4_packetizer.cc
index 9564669..2529a11 100644
--- a/guest/commands/bt_vhci_forwarder/hci/h4_packetizer.cc
+++ b/guest/commands/bt_vhci_forwarder/hci/h4_packetizer.cc
@@ -83,7 +83,8 @@
       return;
     }
 
-    LOG_ALWAYS_FATAL("Read error in %d: %s", h4_parser_.CurrentState(),
+    LOG_ALWAYS_FATAL("Read error in %d: %s",
+                     static_cast<int>(h4_parser_.CurrentState()),
                      strerror(errno));
   }
   h4_parser_.Consume(buffer.data(), bytes_read);
diff --git a/guest/hals/ril/reference-libril/ril_service.cpp b/guest/hals/ril/reference-libril/ril_service.cpp
index f2ff6fd..6d5a51c 100644
--- a/guest/hals/ril/reference-libril/ril_service.cpp
+++ b/guest/hals/ril/reference-libril/ril_service.cpp
@@ -16,11 +16,11 @@
 
 #define LOG_TAG "RILC"
 
-#include "RefRadioSim.h"
 #include "RefImsMedia.h"
 #include "RefRadioIms.h"
 #include "RefRadioModem.h"
 #include "RefRadioNetwork.h"
+#include "RefRadioSim.h"
 
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
@@ -13450,6 +13450,7 @@
                                               std::string("default"));
         publishRadioHal<cf::ril::RefRadioModem>(context, radioHidl, callbackMgr, slot);
         publishRadioHal<cf::ril::RefRadioSim>(context, radioHidl, callbackMgr, slot);
+
         RLOGD("registerService: OemHook is enabled = %s", kOemHookEnabled ? "true" : "false");
         if (kOemHookEnabled) {
             oemHookService[i] = new OemHookImpl;
diff --git a/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp b/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp
index a8def06..5430009 100644
--- a/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp
+++ b/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp
@@ -23,6 +23,7 @@
 #include <sys/inotify.h>
 #include <unistd.h>
 #include <fstream>
+#include <regex>
 
 #include <cutils/properties.h>
 #include <gflags/gflags.h>
@@ -44,7 +45,7 @@
   }
 
   int watch_descriptor = inotify_add_watch(file_close_notification_handle,
-                                           TOMBSTONE_DIR, IN_CLOSE_WRITE);
+                                           TOMBSTONE_DIR, IN_CREATE);
   if (watch_descriptor == -1) {
     ALOGE("%s: Could not add watch for '%s', error: '%s' (%d)", __FUNCTION__,
       TOMBSTONE_DIR, strerror(errno), errno);
@@ -115,11 +116,14 @@
   property_set("tombstone_transmit.init_done", "true");
 #endif
 
+  std::regex re(R"(tombstone_\d+(\.pb)?)");
   while (true) {
     std::vector<std::string> ts_names = cuttlefish::GetFileListFromInotifyFd(
-        tombstone_create_notification_handle, IN_CLOSE_WRITE);
+        tombstone_create_notification_handle, IN_CREATE);
     for (auto& ts_name : ts_names) {
-      tombstone_send_to_host(std::string(TOMBSTONE_DIR) + ts_name);
+      if (regex_match(ts_name, re)) {
+        tombstone_send_to_host(std::string(TOMBSTONE_DIR) + ts_name);
+      }
     }
   }
 
diff --git a/host/commands/assemble_cvd/boot_config.cc b/host/commands/assemble_cvd/boot_config.cc
index d67111c..655e226 100644
--- a/host/commands/assemble_cvd/boot_config.cc
+++ b/host/commands/assemble_cvd/boot_config.cc
@@ -78,7 +78,7 @@
                          std::optional<std::uint16_t> partition_num,
                          std::ostream& env) {
   std::string partition_str =
-      partition_num ? fmt::format("setenv devplist {};", *partition_num) : "";
+      partition_num ? fmt::format("setenv devplist {:x};", *partition_num) : "";
   WritePausedEntrypoint(
       partition_str +
           "load virtio 0:${devplist} ${loadaddr} efi/boot/bootaa64.efi "
@@ -108,13 +108,16 @@
       WriteAndroidEnvironment(env, instance);
       break;
     case CuttlefishConfig::InstanceSpecific::BootFlow::AndroidEfiLoader:
-    case CuttlefishConfig::InstanceSpecific::BootFlow::Linux:
-    case CuttlefishConfig::InstanceSpecific::BootFlow::Fuchsia:
-      WriteEFIEnvironment(instance, {}, env);
-      break;
     case CuttlefishConfig::InstanceSpecific::BootFlow::ChromeOs:
       WriteEFIEnvironment(instance, 2, env);
       break;
+    case CuttlefishConfig::InstanceSpecific::BootFlow::ChromeOsDisk:
+      WriteEFIEnvironment(instance, 12, env);
+      break;
+    case CuttlefishConfig::InstanceSpecific::BootFlow::Fuchsia:
+    case CuttlefishConfig::InstanceSpecific::BootFlow::Linux:
+      WriteEFIEnvironment(instance, {}, env);
+      break;
   }
 
   std::string env_str = env.str();
diff --git a/host/commands/assemble_cvd/disk_builder.cpp b/host/commands/assemble_cvd/disk_builder.cpp
index 189bc2c..b7f6b41 100644
--- a/host/commands/assemble_cvd/disk_builder.cpp
+++ b/host/commands/assemble_cvd/disk_builder.cpp
@@ -46,6 +46,15 @@
   return ret;
 }
 
+DiskBuilder& DiskBuilder::EntireDisk(std::string disk) & {
+  entire_disk_ = std::move(disk);
+  return *this;
+}
+DiskBuilder DiskBuilder::EntireDisk(std::string disk) && {
+  entire_disk_ = std::move(disk);
+  return *this;
+}
+
 DiskBuilder& DiskBuilder::Partitions(std::vector<ImagePartition> partitions) & {
   partitions_ = std::move(partitions);
   return *this;
@@ -133,10 +142,14 @@
   CF_EXPECT(!vm_manager_.empty(), "Missing vm_manager");
   disk_conf << vm_manager_ << "\n";
 
-  CF_EXPECT(!partitions_.empty(), "No partitions");
+  CF_EXPECT(!partitions_.empty() ^ !entire_disk_.empty(),
+            "Specify either partitions or a whole disk");
   for (auto& partition : partitions_) {
     disk_conf << partition.image_file_path << "\n";
   }
+  if (!entire_disk_.empty()) {
+    disk_conf << entire_disk_;
+  }
   return disk_conf.str();
 }
 
@@ -151,7 +164,12 @@
     return true;
   }
 
-  CF_EXPECT(!partitions_.empty(), "No partitions");
+  CF_EXPECT(!partitions_.empty() ^ !entire_disk_.empty(),
+            "Specify either partitions or a whole disk");
+  if (!entire_disk_.empty()) {
+    LOG(DEBUG) << "No composite disk to build";
+    return false;
+  }
   auto last_component_mod_time = LastUpdatedInputDisk(partitions_);
 
   CF_EXPECT(!composite_disk_path_.empty(), "No composite disk path");
@@ -169,6 +187,10 @@
 }
 
 Result<bool> DiskBuilder::BuildCompositeDiskIfNecessary() {
+  if (!entire_disk_.empty()) {
+    LOG(DEBUG) << "No composite disk to build";
+    return false;
+  }
   if (!CF_EXPECT(WillRebuildCompositeDisk())) {
     return false;
   }
diff --git a/host/commands/assemble_cvd/disk_builder.h b/host/commands/assemble_cvd/disk_builder.h
index 7a23a5a..424a5ad 100644
--- a/host/commands/assemble_cvd/disk_builder.h
+++ b/host/commands/assemble_cvd/disk_builder.h
@@ -27,6 +27,9 @@
 
 class DiskBuilder {
  public:
+  DiskBuilder& EntireDisk(std::string path) &;
+  DiskBuilder EntireDisk(std::string path) &&;
+
   DiskBuilder& Partitions(std::vector<ImagePartition> partitions) &;
   DiskBuilder Partitions(std::vector<ImagePartition> partitions) &&;
 
@@ -64,6 +67,7 @@
   Result<std::string> TextConfig();
 
   std::vector<ImagePartition> partitions_;
+  std::string entire_disk_;
   std::string header_path_;
   std::string footer_path_;
   std::string vm_manager_;
diff --git a/host/commands/assemble_cvd/disk_flags.cc b/host/commands/assemble_cvd/disk_flags.cc
index 7d12703..f321def 100644
--- a/host/commands/assemble_cvd/disk_flags.cc
+++ b/host/commands/assemble_cvd/disk_flags.cc
@@ -101,6 +101,8 @@
 DEFINE_string(linux_root_image, CF_DEFAULTS_LINUX_ROOT_IMAGE,
               "Location of linux root filesystem image for cuttlefish otheros flow.");
 
+DEFINE_string(chromeos_disk, CF_DEFAULTS_CHROMEOS_DISK,
+              "Location of a complete ChromeOS GPT disk");
 DEFINE_string(chromeos_kernel_path, CF_DEFAULTS_CHROMEOS_KERNEL_PATH,
               "Location of the chromeos kernel for the chromeos flow.");
 DEFINE_string(chromeos_root_image, CF_DEFAULTS_CHROMEOS_ROOT_IMAGE,
@@ -489,6 +491,8 @@
       return AndroidEfiLoaderCompositeDiskConfig(instance);
     case CuttlefishConfig::InstanceSpecific::BootFlow::ChromeOs:
       return chromeos_composite_disk_config(instance);
+    case CuttlefishConfig::InstanceSpecific::BootFlow::ChromeOsDisk:
+      return {};
     case CuttlefishConfig::InstanceSpecific::BootFlow::Linux:
       return linux_composite_disk_config(instance);
     case CuttlefishConfig::InstanceSpecific::BootFlow::Fuchsia:
@@ -498,15 +502,21 @@
 
 DiskBuilder OsCompositeDiskBuilder(const CuttlefishConfig& config,
     const CuttlefishConfig::InstanceSpecific& instance) {
-  return DiskBuilder()
-      .Partitions(GetOsCompositeDiskConfig(instance))
-      .VmManager(config.vm_manager())
-      .CrosvmPath(instance.crosvm_binary())
-      .ConfigPath(instance.PerInstancePath("os_composite_disk_config.txt"))
+  auto builder =
+      DiskBuilder()
+          .VmManager(config.vm_manager())
+          .CrosvmPath(instance.crosvm_binary())
+          .ConfigPath(instance.PerInstancePath("os_composite_disk_config.txt"))
+          .ResumeIfPossible(FLAGS_resume);
+  if (instance.boot_flow() ==
+      CuttlefishConfig::InstanceSpecific::BootFlow::ChromeOsDisk) {
+    return builder.EntireDisk(instance.chromeos_disk())
+        .CompositeDiskPath(instance.chromeos_disk());
+  }
+  return builder.Partitions(GetOsCompositeDiskConfig(instance))
       .HeaderPath(instance.PerInstancePath("os_composite_gpt_header.img"))
       .FooterPath(instance.PerInstancePath("os_composite_gpt_footer.img"))
-      .CompositeDiskPath(instance.os_composite_disk_path())
-      .ResumeIfPossible(FLAGS_resume);
+      .CompositeDiskPath(instance.os_composite_disk_path());
 }
 
 DiskBuilder ApCompositeDiskBuilder(const CuttlefishConfig& config,
@@ -715,6 +725,8 @@
   std::vector<std::string> android_efi_loader =
       android::base::Split(FLAGS_android_efi_loader, ",");
 
+  std::vector<std::string> chromeos_disk =
+      android::base::Split(FLAGS_chromeos_disk, ",");
   std::vector<std::string> chromeos_kernel_path =
       android::base::Split(FLAGS_chromeos_kernel_path, ",");
   std::vector<std::string> chromeos_root_image =
@@ -824,6 +836,11 @@
     } else {
       instance.set_android_efi_loader(android_efi_loader[instance_index]);
     }
+    if (instance_index >= chromeos_disk.size()) {
+      instance.set_chromeos_disk(chromeos_disk[0]);
+    } else {
+      instance.set_chromeos_disk(chromeos_disk[instance_index]);
+    }
     if (instance_index >= chromeos_kernel_path.size()) {
       instance.set_chromeos_kernel_path(chromeos_kernel_path[0]);
     } else {
diff --git a/host/commands/assemble_cvd/flags_defaults.h b/host/commands/assemble_cvd/flags_defaults.h
index ecf1aa9..aac68a8 100644
--- a/host/commands/assemble_cvd/flags_defaults.h
+++ b/host/commands/assemble_cvd/flags_defaults.h
@@ -42,11 +42,7 @@
 #define CF_DEFAULTS_DAEMON false
 #define CF_DEFAULTS_VM_MANAGER CF_DEFAULTS_DYNAMIC_STRING
 #define CF_DEFAULTS_VSOCK_GUEST_CID cuttlefish::GetDefaultVsockCid()
-#ifdef VHOST_USER_VSOCK_BY_DEFAULT
-#define CF_DEFAULTS_VHOST_USER_VSOCK true
-#else
 #define CF_DEFAULTS_VHOST_USER_VSOCK false
-#endif  // VHOST_USER_VSOCK_BY_DEFAULT
 #define CF_DEFAULTS_ENABLE_MINIMAL_MODE false
 #define CF_DEFAULTS_RESTART_SUBPROCESSES false
 #define CF_DEFAULTS_SETUPWIZARD_MODE "DISABLED"
@@ -72,7 +68,7 @@
 #define CF_DEFAULTS_ENABLE_VIRTIOFS false
 
 // Qemu default parameters
-#define CF_DEFAULTS_QEMU_BINARY_DIR "/usr/bin"
+#define CF_DEFAULTS_QEMU_BINARY_DIR cuttlefish::DefaultQemuBinaryDir()
 
 // Gem5 default parameters
 #define CF_DEFAULTS_GEM5_BINARY_DIR HostBinaryPath("gem5")
@@ -113,6 +109,7 @@
 #define CF_DEFAULTS_DATA_IMAGE CF_DEFAULTS_DYNAMIC_STRING
 #define CF_DEFAULTS_INIT_BOOT_IMAGE CF_DEFAULTS_DYNAMIC_STRING
 #define CF_DEFAULTS_ANDROID_EFI_LOADER CF_DEFAULTS_DYNAMIC_STRING
+#define CF_DEFAULTS_CHROMEOS_DISK ""
 #define CF_DEFAULTS_CHROMEOS_KERNEL_PATH ""
 #define CF_DEFAULTS_CHROMEOS_ROOT_IMAGE ""
 #define CF_DEFAULTS_LINUX_INITRAMFS_PATH CF_DEFAULTS_DYNAMIC_STRING
@@ -133,7 +130,7 @@
 
 // Policy default parameters
 #define CF_DEFAULTS_DATA_POLICY "use_existing"
-#define CF_DEFAULTS_USERDATA_FORMAT "f2fs"
+#define CF_DEFAULTS_USERDATA_FORMAT USERDATA_FILE_SYSTEM_TYPE
 #define CF_DEFAULTS_BLANK_DATA_IMAGE_MB CF_DEFAULTS_DYNAMIC_INT
 
 // Graphics default parameters
diff --git a/host/commands/cvd/client.cpp b/host/commands/cvd/client.cpp
index b797da7..bbd738b 100644
--- a/host/commands/cvd/client.cpp
+++ b/host/commands/cvd/client.cpp
@@ -28,6 +28,7 @@
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/environment.h"
+#include "common/libs/utils/proto.h"
 #include "common/libs/utils/result.h"
 #include "common/libs/utils/subprocess.h"
 #include "host/commands/cvd/common_utils.h"
@@ -189,7 +190,7 @@
   return connection;
 }
 
-cvd::Version CvdClient::GetClientVersion() {
+static cvd::Version ClientVersion() {
   cvd::Version client_version;
   client_version.set_major(cvd::kVersionMajor);
   client_version.set_minor(cvd::kVersionMinor);
@@ -496,18 +497,8 @@
 }
 
 Result<std::string> CvdClient::HandleVersion() {
-  using google::protobuf::TextFormat;
-  std::stringstream result;
-  std::string output;
-  auto server_version = CF_EXPECT(GetServerVersion());
-  CF_EXPECT(TextFormat::PrintToString(server_version, &output),
-            "converting server_version to string failed");
-  result << "Server version:" << std::endl << std::endl << output << std::endl;
-
-  CF_EXPECT(TextFormat::PrintToString(CvdClient::GetClientVersion(), &output),
-            "converting client version to string failed");
-  result << "Client version:" << std::endl << std::endl << output << std::endl;
-  return {result.str()};
+  return fmt::format("Server version:\n\n{}\nClient version:\n\n{}\n",
+                     CF_EXPECT(GetServerVersion()), ClientVersion());
 }
 
 Result<Json::Value> CvdClient::ListSubcommands(const cvd_common::Envs& envs) {
diff --git a/host/commands/cvd/client.h b/host/commands/cvd/client.h
index f936504..acc5866 100644
--- a/host/commands/cvd/client.h
+++ b/host/commands/cvd/client.h
@@ -83,7 +83,6 @@
 
   Result<Json::Value> ListSubcommands(const cvd_common::Envs& envs);
   Result<SharedFD> ConnectToServer();
-  static cvd::Version GetClientVersion();
 
   Result<void> RestartServer(const cvd::Version& server_version);
 
diff --git a/host/commands/cvd/command_sequence.cpp b/host/commands/cvd/command_sequence.cpp
index b44834b..c754ec6 100644
--- a/host/commands/cvd/command_sequence.cpp
+++ b/host/commands/cvd/command_sequence.cpp
@@ -137,4 +137,9 @@
   return std::vector<std::string>{subcmds.begin(), subcmds.end()};
 }
 
+Result<CvdServerHandler*> CommandSequenceExecutor::GetHandler(
+    const RequestWithStdio& request) {
+  return CF_EXPECT(RequestHandler(request, server_handlers_));
+}
+
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/command_sequence.h b/host/commands/cvd/command_sequence.h
index 427802c..49d50b7 100644
--- a/host/commands/cvd/command_sequence.h
+++ b/host/commands/cvd/command_sequence.h
@@ -36,6 +36,7 @@
   Result<cvd::Response> ExecuteOne(const RequestWithStdio&, SharedFD report);
 
   std::vector<std::string> CmdList() const;
+  Result<CvdServerHandler*> GetHandler(const RequestWithStdio& request);
 
  private:
   const std::vector<std::unique_ptr<CvdServerHandler>>& server_handlers_;
diff --git a/host/commands/cvd/instance_manager.cpp b/host/commands/cvd/instance_manager.cpp
index 17f2a9b..de64f23 100644
--- a/host/commands/cvd/instance_manager.cpp
+++ b/host/commands/cvd/instance_manager.cpp
@@ -36,6 +36,7 @@
 #include "common/libs/utils/subprocess.h"
 #include "cvd_server.pb.h"
 #include "host/commands/cvd/common_utils.h"
+#include "host/commands/cvd/selector/instance_database_types.h"
 #include "host/commands/cvd/selector/instance_database_utils.h"
 #include "host/commands/cvd/selector/selector_constants.h"
 #include "host/commands/cvd/server_constants.h"
@@ -155,12 +156,12 @@
   const auto host_artifacts_path = group_info.host_artifacts_path;
   const auto product_out_path = group_info.product_out_path;
   const auto& per_instance_info = group_info.instances;
-
-  auto new_group = CF_EXPECT(
-      instance_db.AddInstanceGroup({.group_name = group_name,
-                                    .home_dir = home_dir,
-                                    .host_artifacts_path = host_artifacts_path,
-                                    .product_out_path = product_out_path}));
+  auto new_group = CF_EXPECT(instance_db.AddInstanceGroup(
+      {.group_name = group_name,
+       .home_dir = home_dir,
+       .host_artifacts_path = host_artifacts_path,
+       .product_out_path = product_out_path,
+       .start_time = selector::CvdServerClock::now()}));
 
   using InstanceInfo = selector::InstanceDatabase::InstanceInfo;
   std::vector<InstanceInfo> instances_info;
@@ -349,7 +350,7 @@
   return *(output.begin());
 }
 
-std::vector<std::string> InstanceManager::AllGroupNames(const uid_t uid) const {
+std::vector<std::string> InstanceManager::AllGroupNames(uid_t uid) const {
   std::lock_guard lock(instance_db_mutex_);
   if (!Contains(instance_dbs_, uid)) {
     return {};
@@ -364,4 +365,34 @@
   return group_names;
 }
 
+Result<InstanceManager::UserGroupSelectionSummary>
+InstanceManager::GroupSummaryMenu(uid_t uid) const {
+  std::lock_guard lock(instance_db_mutex_);
+  CF_EXPECT(Contains(instance_dbs_, uid));
+  const auto& db = instance_dbs_.at(uid);
+
+  UserGroupSelectionSummary summary;
+
+  // List of Cuttlefish Instance Groups:
+  //   [i] : group_name (created: TIME)
+  //      <a> instance0.device_name() (id: instance_id)
+  //      <b> instance1.device_name() (id: instance_id)
+  std::stringstream ss;
+  ss << "List of Cuttlefish Instance Groups:" << std::endl;
+  int group_idx = 0;
+  for (const auto& group : db.InstanceGroups()) {
+    fmt::print(ss, "  [{}] : {} (created: {})\n", group_idx, group->GroupName(),
+               selector::Format(group->StartTime()));
+    summary.idx_to_group_name[group_idx] = group->GroupName();
+    char instance_idx = 'a';
+    for (const auto& instance : CF_EXPECT(group->FindAllInstances())) {
+      fmt::print(ss, "    <{}> {} (id : {})\n", instance_idx++,
+                 instance.Get().DeviceName(), instance.Get().InstanceId());
+    }
+    group_idx++;
+  }
+  summary.menu = ss.str();
+  return summary;
+}
+
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/instance_manager.h b/host/commands/cvd/instance_manager.h
index fa51dcf..45a7ddc 100644
--- a/host/commands/cvd/instance_manager.h
+++ b/host/commands/cvd/instance_manager.h
@@ -103,7 +103,16 @@
                                        const Queries& queries) const;
   Result<Json::Value> Serialize(const uid_t uid);
   Result<void> LoadFromJson(const uid_t uid, const Json::Value&);
-  std::vector<std::string> AllGroupNames(const uid_t uid) const;
+  std::vector<std::string> AllGroupNames(uid_t uid) const;
+
+  struct UserGroupSelectionSummary {
+    // Index to group name. This is the index printed in the menu
+    // This field offers mapping between the number/index the user
+    // selects and the group that is to be chosen
+    std::unordered_map<int, std::string> idx_to_group_name;
+    std::string menu;
+  };
+  Result<UserGroupSelectionSummary> GroupSummaryMenu(uid_t uid) const;
 
  private:
   Result<void> IssueStopCommand(const SharedFD& out, const SharedFD& err,
diff --git a/host/commands/cvd/logger.cpp b/host/commands/cvd/logger.cpp
index 68e2691..890af67 100644
--- a/host/commands/cvd/logger.cpp
+++ b/host/commands/cvd/logger.cpp
@@ -117,7 +117,8 @@
   auto output_string =
       StderrOutputGenerator(now, getpid(), android::base::GetThreadId(),
                             severity, tag, file, line, message);
-  WriteAll(target_, output_string);
+  const bool color = target_->IsOpen() && target_->IsATTY();
+  WriteAll(target_, (color ? output_string : StripColorCodes(output_string)));
 }
 
 void ServerLogger::ScopedLogger::SetSeverity(const LogSeverity severity) {
diff --git a/host/commands/cvd/main.cc b/host/commands/cvd/main.cc
index 998a083..2e1959a 100644
--- a/host/commands/cvd/main.cc
+++ b/host/commands/cvd/main.cc
@@ -200,7 +200,8 @@
     // TODO: we should not print the stack trace, instead, we should rely on
     // each handler to print the error message directly in the client's
     // std::cerr. We print the stack trace only in the verbose mode.
-    std::cerr << result.error().FormatForEnv() << std::endl;
+    std::cerr << result.error().FormatForEnv(isatty(STDERR_FILENO))
+              << std::endl;
     // TODO(kwstephenkim): better coloring
     constexpr char kUserReminder[] =
         R"(    If the error above is unclear, please copy the text into an issue at:)";
diff --git a/host/commands/cvd/metrics/cvd_metrics_api.cpp b/host/commands/cvd/metrics/cvd_metrics_api.cpp
index ec41765..58e144c 100644
--- a/host/commands/cvd/metrics/cvd_metrics_api.cpp
+++ b/host/commands/cvd/metrics/cvd_metrics_api.cpp
@@ -34,6 +34,9 @@
 constexpr int kCppClientType =
     19;  // C++ native client type (clientanalytics.proto)
 
+const std::string kInternalEmail = "@google.com";
+const std::vector<std::string> kInternalHostname = {"google"};
+
 std::string GenerateUUID() {
   uuid_t uuid;
   uuid_generate_random(uuid);
@@ -103,6 +106,42 @@
   return commandLine;
 }
 
+std::string GetUserEmail() {
+  std::string email;
+  FILE* pipe = popen("git config --get user.email 2>/dev/null", "r");
+  if (!pipe) {
+    LOG(ERROR) << "popen() failed!";
+    return "";
+  }
+  char buffer[128];
+  while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
+    email += buffer;
+  }
+  pclose(pipe);
+  email.erase(std::remove(email.begin(), email.end(), '\n'), email.end());
+  return email;
+}
+
+UserType GetUserType() {
+  std::string email = GetUserEmail();
+  if (email.find(kInternalEmail) != std::string::npos) {
+    return UserType::GOOGLE;
+  }
+
+  char hostname[1024];
+  hostname[1023] = '\0';
+  gethostname(hostname, sizeof(hostname) - 1);
+  std::string host_str(hostname);
+
+  for (const auto& internal_host : kInternalHostname) {
+    if (host_str.find(internal_host) != std::string::npos) {
+      return UserType::GOOGLE;
+    }
+  }
+
+  return UserType::EXTERNAL;
+}
+
 }  // namespace
 
 int CvdMetrics::SendLaunchCommand(const std::string& command_line) {
@@ -124,6 +163,9 @@
 }
 
 int CvdMetrics::SendCvdMetrics(const std::vector<std::string>& args) {
+  if (GetUserType() != UserType::GOOGLE) {
+    return 0;
+  }
   std::string command_line = createCommandLine(args);
   return CvdMetrics::SendLaunchCommand(command_line);
 }
diff --git a/host/commands/cvd/metrics/proto/clientanalytics.proto b/host/commands/cvd/metrics/proto/clientanalytics.proto
index a120a91..e088384 100644
--- a/host/commands/cvd/metrics/proto/clientanalytics.proto
+++ b/host/commands/cvd/metrics/proto/clientanalytics.proto
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 syntax = "proto2";
+package cuttlefish;
 
 message LogRequest {
   optional ClientInfo client_info = 1;
diff --git a/host/commands/cvd/metrics/utils.h b/host/commands/cvd/metrics/utils.h
index ff4d40d..283d6ff 100644
--- a/host/commands/cvd/metrics/utils.h
+++ b/host/commands/cvd/metrics/utils.h
@@ -35,7 +35,7 @@
 std::string GetCompany();
 std::string GetVmmVersion();
 uint64_t GetEpochTimeMs();
-std::string ProtoToString(LogEvent* event);
+std::string ProtoToString(cuttlefish::LogEvent* event);
 cuttlefish::MetricsExitCodes PostRequest(const std::string& output,
                                          ClearcutServer server);
 
diff --git a/host/commands/cvd/parser/load_configs_parser.cpp b/host/commands/cvd/parser/load_configs_parser.cpp
index 79a90c4..0197f6d 100644
--- a/host/commands/cvd/parser/load_configs_parser.cpp
+++ b/host/commands/cvd/parser/load_configs_parser.cpp
@@ -154,7 +154,6 @@
 
 std::vector<Flag> GetFlagsVector(LoadFlags& load_flags) {
   std::vector<Flag> flags;
-  flags.emplace_back(GflagsCompatFlag("help", load_flags.help));
   flags.emplace_back(
       GflagsCompatFlag("credential_source", load_flags.credential_source));
   flags.emplace_back(
@@ -181,42 +180,9 @@
   path.insert(0, working_dir + "/");
 }
 
-}  // namespace
-
-Result<LoadFlags> GetFlags(std::vector<std::string>& args,
-                           const std::string& working_directory) {
-  LoadFlags load_flags;
-  auto flags = GetFlagsVector(load_flags);
-  CF_EXPECT(ParseFlags(flags, args));
-  CF_EXPECT(load_flags.help || args.size() > 0,
-            "No arguments provided to cvd load command, please provide at "
-            "least one argument (help or path to json file)");
-
-  if (load_flags.base_dir.empty()) {
-    load_flags.base_dir = DefaultBaseDir();
-  }
-  MakeAbsolute(load_flags.base_dir, working_directory);
-
-  load_flags.config_path = args.front();
-  MakeAbsolute(load_flags.config_path, working_directory);
-
-  if (!load_flags.credential_source.empty()) {
-    for (const auto& flag : load_flags.overrides) {
-      CF_EXPECT(!android::base::StartsWith(flag.config_path,
-                                           kCredentialSourceOverride),
-                "Specifying both --override=fetch.credential_source and the "
-                "--credential_source flag is not allowed.");
-    }
-    load_flags.overrides.emplace_back(
-        Override{.config_path = std::string(kCredentialSourceOverride),
-                 .new_value = load_flags.credential_source});
-  }
-  return load_flags;
-}
-
 Result<Json::Value> ParseJsonFile(const std::string& file_path) {
   CF_EXPECTF(FileExists(file_path),
-             "Provided file \"{}\" to cvd load does not exist", file_path);
+             "Provided file \"{}\" to cvd command does not exist", file_path);
 
   std::string file_content;
   using android::base::ReadFileToString;
@@ -258,12 +224,6 @@
   return result;
 }
 
-std::ostream& operator<<(std::ostream& out, const Override& override) {
-  fmt::print(out, "(config_path=\"{}\", new_value=\"{}\")",
-             override.config_path, override.new_value);
-  return out;
-}
-
 Result<LoadDirectories> GenerateLoadDirectories(
     const std::string& parent_directory,
     std::vector<std::string>& system_image_path_configs,
@@ -319,7 +279,63 @@
                   .selector_flags = CF_EXPECT(ParseSelectorConfigs(root)),
                   .fetch_cvd_flags = CF_EXPECT(ParseFetchCvdConfigs(
                       root, load_directories.target_directory,
-                      load_directories.target_subdirectories))};
+                      load_directories.target_subdirectories)),
+                  .load_directories = load_directories};
+}
+
+}  // namespace
+
+std::ostream& operator<<(std::ostream& out, const Override& override) {
+  fmt::print(out, "(config_path=\"{}\", new_value=\"{}\")",
+             override.config_path, override.new_value);
+  return out;
+}
+
+Result<LoadFlags> GetFlags(std::vector<std::string>& args,
+                           const std::string& working_directory) {
+  LoadFlags load_flags;
+  auto flags = GetFlagsVector(load_flags);
+  CF_EXPECT(ParseFlags(flags, args));
+  CF_EXPECT(
+      args.size() > 0,
+      "No arguments provided to cvd command, please provide path to json file");
+
+  if (load_flags.base_dir.empty()) {
+    load_flags.base_dir = DefaultBaseDir();
+  }
+  MakeAbsolute(load_flags.base_dir, working_directory);
+
+  load_flags.config_path = args.front();
+  MakeAbsolute(load_flags.config_path, working_directory);
+
+  if (!load_flags.credential_source.empty()) {
+    for (const auto& flag : load_flags.overrides) {
+      CF_EXPECT(!android::base::StartsWith(flag.config_path,
+                                           kCredentialSourceOverride),
+                "Specifying both --override=fetch.credential_source and the "
+                "--credential_source flag is not allowed.");
+    }
+    load_flags.overrides.emplace_back(
+        Override{.config_path = std::string(kCredentialSourceOverride),
+                 .new_value = load_flags.credential_source});
+  }
+  return load_flags;
+}
+
+Result<CvdFlags> GetCvdFlags(const LoadFlags& flags) {
+  Json::Value json_configs =
+      CF_EXPECT(GetOverriddenConfig(flags.config_path, flags.overrides));
+
+  std::vector<std::string> system_image_path_configs =
+      CF_EXPECT(GetConfiguredSystemImagePaths(json_configs));
+  std::optional<std::string> host_package_dir =
+      GetConfiguredSystemHostPath(json_configs);
+
+  const auto load_directories = CF_EXPECT(GenerateLoadDirectories(
+      flags.base_dir, system_image_path_configs, host_package_dir,
+      json_configs["instances"].size()));
+  return CF_EXPECT(ParseCvdConfigs(json_configs, load_directories),
+                   "Parsing json configs failed");
 }
 
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/parser/load_configs_parser.h b/host/commands/cvd/parser/load_configs_parser.h
index b279332..15d1f57 100644
--- a/host/commands/cvd/parser/load_configs_parser.h
+++ b/host/commands/cvd/parser/load_configs_parser.h
@@ -26,12 +26,6 @@
 
 namespace cuttlefish {
 
-typedef struct _CvdFlags {
-  std::vector<std::string> launch_cvd_flags;
-  std::vector<std::string> selector_flags;
-  std::vector<std::string> fetch_cvd_flags;
-} CvdFlags;
-
 struct LoadDirectories {
   std::string target_directory;
   std::vector<std::string> target_subdirectories;
@@ -40,6 +34,13 @@
   std::string system_image_directory_flag;
 };
 
+struct CvdFlags {
+  std::vector<std::string> launch_cvd_flags;
+  std::vector<std::string> selector_flags;
+  std::vector<std::string> fetch_cvd_flags;
+  LoadDirectories load_directories;
+};
+
 struct Override {
   std::string config_path;
   std::string new_value;
@@ -48,7 +49,6 @@
 std::ostream& operator<<(std::ostream& out, const Override& override);
 
 struct LoadFlags {
-  bool help = false;
   std::vector<Override> overrides;
   std::string config_path;
   std::string credential_source;
@@ -58,22 +58,6 @@
 Result<LoadFlags> GetFlags(std::vector<std::string>& args,
                            const std::string& working_directory);
 
-Result<Json::Value> ParseJsonFile(const std::string& file_path);
-
-Result<std::vector<std::string>> GetConfiguredSystemImagePaths(
-    Json::Value& root);
-std::optional<std::string> GetConfiguredSystemHostPath(Json::Value& root);
-
-Result<Json::Value> GetOverriddenConfig(
-    const std::string& config_path,
-    const std::vector<Override>& override_flags);
-
-Result<LoadDirectories> GenerateLoadDirectories(
-    const std::string& parent_directory,
-    std::vector<std::string>& system_image_path_configs,
-    std::optional<std::string> system_host_path, const int num_instances);
-
-Result<CvdFlags> ParseCvdConfigs(Json::Value& root,
-                                 const LoadDirectories& load_directories);
+Result<CvdFlags> GetCvdFlags(const LoadFlags& flags);
 
 };  // namespace cuttlefish
diff --git a/host/commands/cvd/request_context.cpp b/host/commands/cvd/request_context.cpp
index 8be2ed7..146947f 100644
--- a/host/commands/cvd/request_context.cpp
+++ b/host/commands/cvd/request_context.cpp
@@ -21,6 +21,7 @@
 
 #include <android-base/logging.h>
 
+#include "common/libs/utils/result.h"
 #include "host/commands/cvd/command_sequence.h"
 #include "host/commands/cvd/instance_manager.h"
 #include "host/commands/cvd/server_client.h"
@@ -36,6 +37,7 @@
 #include "host/commands/cvd/server_command/handler_proxy.h"
 #include "host/commands/cvd/server_command/help.h"
 #include "host/commands/cvd/server_command/host_tool_target_manager.h"
+#include "host/commands/cvd/server_command/lint.h"
 #include "host/commands/cvd/server_command/load_configs.h"
 #include "host/commands/cvd/server_command/power.h"
 #include "host/commands/cvd/server_command/reset.h"
@@ -83,7 +85,8 @@
       instance_manager_, subprocess_waiter_, host_tool_target_manager_));
   request_handlers_.emplace_back(
       NewCvdServerHandlerProxy(command_sequence_executor_));
-  request_handlers_.emplace_back(NewCvdHelpHandler(command_sequence_executor_));
+  request_handlers_.emplace_back(NewCvdHelpHandler(this->request_handlers_));
+  request_handlers_.emplace_back(NewLintCommand());
   request_handlers_.emplace_back(
       NewLoadConfigsCommand(command_sequence_executor_));
   request_handlers_.emplace_back(NewCvdDevicePowerCommandHandler(
diff --git a/host/commands/cvd/request_context.h b/host/commands/cvd/request_context.h
index 7b73757..339ba5e 100644
--- a/host/commands/cvd/request_context.h
+++ b/host/commands/cvd/request_context.h
@@ -19,6 +19,7 @@
 #include <atomic>
 #include <vector>
 
+#include "common/libs/utils/result.h"
 #include "host/commands/cvd/command_sequence.h"
 #include "host/commands/cvd/instance_manager.h"
 #include "host/commands/cvd/server_client.h"
diff --git a/host/commands/cvd/selector/instance_database.h b/host/commands/cvd/selector/instance_database.h
index 80ce576..7459084 100644
--- a/host/commands/cvd/selector/instance_database.h
+++ b/host/commands/cvd/selector/instance_database.h
@@ -39,6 +39,8 @@
   using ConstInstanceHandler = ConstHandler<LocalInstance>;
 
  public:
+  static constexpr const char kJsonGroups[] = "Groups";
+
   InstanceDatabase();
   bool IsEmpty() const;
 
@@ -47,6 +49,7 @@
     std::string home_dir;
     std::string host_artifacts_path;
     std::string product_out_path;
+    TimeStamp start_time;
   };
   /** Adds instance group.
    *
@@ -149,8 +152,6 @@
   std::vector<std::unique_ptr<LocalInstanceGroup>> local_instance_groups_;
   Map<FieldName, ConstGroupHandler> group_handlers_;
   Map<FieldName, ConstInstanceHandler> instance_handlers_;
-
-  static constexpr const char kJsonGroups[] = "Groups";
 };
 
 }  // namespace selector
diff --git a/host/commands/cvd/selector/instance_database_impl.cpp b/host/commands/cvd/selector/instance_database_impl.cpp
index 9001045..73dc561 100644
--- a/host/commands/cvd/selector/instance_database_impl.cpp
+++ b/host/commands/cvd/selector/instance_database_impl.cpp
@@ -65,7 +65,8 @@
       new LocalInstanceGroup({.group_name = param.group_name,
                               .home_dir = param.home_dir,
                               .host_artifacts_path = param.host_artifacts_path,
-                              .product_out_path = param.product_out_path});
+                              .product_out_path = param.product_out_path,
+                              .start_time = param.start_time});
   CF_EXPECT(new_group != nullptr);
   local_instance_groups_.emplace_back(new_group);
   const auto raw_ptr = local_instance_groups_.back().get();
@@ -295,11 +296,26 @@
       group_json[LocalInstanceGroup::kJsonHostArtifactPath].asString();
   const std::string product_out_path =
       group_json[LocalInstanceGroup::kJsonProductOutPath].asString();
+  TimeStamp start_time = CvdServerClock::now();
+
+  // test if the field is available as the field has been added
+  // recently as of b/315855286
+  if (group_json.isMember(LocalInstanceGroup::kJsonStartTime)) {
+    auto restored_start_time_result = DeserializeTimePoint(group_json);
+    if (restored_start_time_result.ok()) {
+      start_time = std::move(*restored_start_time_result);
+    } else {
+      LOG(ERROR) << "Start time restoration from json failed, so we use "
+                 << " the current system time. Reasons: "
+                 << restored_start_time_result.error().FormatForEnv();
+    }
+  }
   const auto new_group_ref =
       CF_EXPECT(AddInstanceGroup({.group_name = group_name,
                                   .home_dir = home_dir,
                                   .host_artifacts_path = host_artifacts_path,
-                                  .product_out_path = product_out_path}));
+                                  .product_out_path = product_out_path,
+                                  .start_time = std::move(start_time)}));
   android::base::ScopeGuard remove_already_added_new_group(
       [&new_group_ref, this]() {
         this->RemoveInstanceGroup(new_group_ref.Get());
diff --git a/host/commands/cvd/selector/instance_database_types.cpp b/host/commands/cvd/selector/instance_database_types.cpp
index 994917c..bb7920e 100644
--- a/host/commands/cvd/selector/instance_database_types.cpp
+++ b/host/commands/cvd/selector/instance_database_types.cpp
@@ -16,11 +16,49 @@
 
 #include "host/commands/cvd/selector/instance_database_types.h"
 
+#include <android-base/parseint.h>
+#include <fmt/core.h>
+
+#include "host/commands/cvd/selector/instance_group_record.h"
+
 namespace cuttlefish {
 namespace selector {
 
 Query::Query(const std::string& field_name, const std::string& field_value)
     : field_name_(field_name), field_value_(field_value) {}
 
+std::string SerializeTimePoint(const TimeStamp& present) {
+  const auto duration =
+      std::chrono::duration_cast<CvdTimeDuration>(present.time_since_epoch());
+  return fmt::format("{}", duration.count());
+}
+
+Result<TimeStamp> DeserializeTimePoint(const Json::Value& group_json) {
+  std::string group_name = "unknown";
+  if (group_json.isMember(LocalInstanceGroup::kJsonGroupName)) {
+    group_name = group_json[LocalInstanceGroup::kJsonGroupName].asString();
+  }
+  CF_EXPECTF(group_json.isMember(LocalInstanceGroup::kJsonStartTime),
+             "The serialized instance database in json file for group \"{}\""
+             " is missing the start time field: {}",
+             group_name, LocalInstanceGroup::kJsonStartTime);
+  std::string serialized(
+      group_json[LocalInstanceGroup::kJsonStartTime].asString());
+
+  using CountType = decltype(((const CvdTimeDuration*)nullptr)->count());
+  CountType count = 0;
+  CF_EXPECTF(android::base::ParseInt(serialized, &count),
+             "Failed to serialize: {}", serialized);
+  CvdTimeDuration duration(count);
+  TimeStamp restored_time(duration);
+  LOG(VERBOSE) << "The start time of the group \"" << group_name
+               << "\" is restored as: " << Format(restored_time);
+  return restored_time;
+}
+
+std::string Format(const TimeStamp& time_point) {
+  return fmt::format("{:%b-%d-%Y %H:%M:%S}", time_point);
+}
+
 }  // namespace selector
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/selector/instance_database_types.h b/host/commands/cvd/selector/instance_database_types.h
index 7710483..7b69b76 100644
--- a/host/commands/cvd/selector/instance_database_types.h
+++ b/host/commands/cvd/selector/instance_database_types.h
@@ -16,12 +16,17 @@
 
 #pragma once
 
+#include <chrono>
+#include <iostream>
 #include <string>
 #include <type_traits>
 #include <unordered_map>
 #include <unordered_set>
 #include <vector>
 
+#include "common/libs/utils/json.h"
+#include "common/libs/utils/result.h"
+
 namespace cuttlefish {
 namespace selector {
 namespace selector_impl {
@@ -62,5 +67,13 @@
 template <typename K, typename V>
 using Map = std::unordered_map<K, V>;
 
+using CvdServerClock = std::chrono::system_clock;
+using TimeStamp = std::chrono::time_point<CvdServerClock>;
+using CvdTimeDuration = std::chrono::milliseconds;
+
+std::string SerializeTimePoint(const TimeStamp&);
+Result<TimeStamp> DeserializeTimePoint(const Json::Value& group_json);
+std::string Format(const TimeStamp&);
+
 }  // namespace selector
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/selector/instance_group_record.cpp b/host/commands/cvd/selector/instance_group_record.cpp
index 6db8122..d07b586 100644
--- a/host/commands/cvd/selector/instance_group_record.cpp
+++ b/host/commands/cvd/selector/instance_group_record.cpp
@@ -16,6 +16,7 @@
 
 #include "host/commands/cvd/selector/instance_group_record.h"
 
+#include "host/commands/cvd/selector/instance_database_types.h"
 #include "host/commands/cvd/selector/instance_database_utils.h"
 #include "host/commands/cvd/selector/selector_constants.h"
 
@@ -27,7 +28,11 @@
       host_artifacts_path_{param.host_artifacts_path},
       product_out_path_{param.product_out_path},
       internal_group_name_(GenInternalGroupName()),
-      group_name_(param.group_name) {}
+      group_name_(param.group_name),
+      start_time_(param.start_time) {
+  LOG(VERBOSE) << "Creating a group \"" << group_name_ << "\" ("
+               << Format(start_time_) << ")";
+}
 
 LocalInstanceGroup::LocalInstanceGroup(const LocalInstanceGroup& src)
     : home_dir_{src.home_dir_},
@@ -35,6 +40,7 @@
       product_out_path_{src.product_out_path_},
       internal_group_name_{src.internal_group_name_},
       group_name_{src.group_name_},
+      start_time_{src.start_time_},
       instances_{CopyInstances(src.instances_)} {}
 
 LocalInstanceGroup& LocalInstanceGroup::operator=(
@@ -133,6 +139,8 @@
   group_json[kJsonHomeDir] = home_dir_;
   group_json[kJsonHostArtifactPath] = host_artifacts_path_;
   group_json[kJsonProductOutPath] = product_out_path_;
+  group_json[kJsonStartTime] = SerializeTimePoint(start_time_);
+
   int i = 0;
   Json::Value instances_array_json;
   for (const auto& instance : instances_) {
diff --git a/host/commands/cvd/selector/instance_group_record.h b/host/commands/cvd/selector/instance_group_record.h
index db86a6f..91103bd 100644
--- a/host/commands/cvd/selector/instance_group_record.h
+++ b/host/commands/cvd/selector/instance_group_record.h
@@ -54,6 +54,7 @@
     return instances_;
   }
   Json::Value Serialize() const;
+  auto StartTime() const { return start_time_; }
 
   /**
    * return error if instance id of instance is taken AND that taken id
@@ -76,12 +77,21 @@
   // returns all instances in the dedicated data type
   Result<Set<ConstRef<LocalInstance>>> FindAllInstances() const;
 
+  static constexpr const char kJsonGroupName[] = "Group Name";
+  static constexpr const char kJsonHomeDir[] = "Runtime/Home Dir";
+  static constexpr const char kJsonHostArtifactPath[] = "Host Tools Dir";
+  static constexpr const char kJsonProductOutPath[] = "Product Out Dir";
+  static constexpr const char kJsonStartTime[] = "Start Time";
+  static constexpr const char kJsonInstances[] = "Instances";
+  static constexpr const char kJsonParent[] = "Parent Group";
+
  private:
   struct InstanceGroupParam {
     std::string group_name;
     std::string home_dir;
     std::string host_artifacts_path;
     std::string product_out_path;
+    TimeStamp start_time;
   };
   LocalInstanceGroup(const InstanceGroupParam& param);
   // Eventually copies the instances of a src to *this
@@ -96,18 +106,9 @@
   // for now, "cvd", which is "cvd-".remove_suffix(1)
   std::string internal_group_name_;
   std::string group_name_;
-  // This will be initialized after the LocalInstanceGroup is created,
-  // which is also after the device completes the boot.
-  std::optional<std::string> build_id_;
+  TimeStamp start_time_;
   Set<std::unique_ptr<LocalInstance>> instances_;
 
-  static constexpr const char kJsonGroupName[] = "Group Name";
-  static constexpr const char kJsonHomeDir[] = "Runtime/Home Dir";
-  static constexpr const char kJsonHostArtifactPath[] = "Host Tools Dir";
-  static constexpr const char kJsonProductOutPath[] = "Product Out Dir";
-  static constexpr const char kJsonInstances[] = "Instances";
-  static constexpr const char kJsonParent[] = "Parent Group";
-
   /*
    * Expose constructor to the tests in InstanceRecord unit test suite.
    *
diff --git a/host/commands/cvd/selector/instance_record.cpp b/host/commands/cvd/selector/instance_record.cpp
index 82312d8..debc311 100644
--- a/host/commands/cvd/selector/instance_record.cpp
+++ b/host/commands/cvd/selector/instance_record.cpp
@@ -67,13 +67,15 @@
           .home_dir = src.ParentGroup().HomeDir(),
           .host_artifacts_path = src.ParentGroup().HostArtifactsPath(),
           .internal_group_name = src.ParentGroup().InternalGroupName(),
-          .group_name = src.ParentGroup().GroupName()}} {}
+          .group_name = src.ParentGroup().GroupName(),
+          .start_time = src.ParentGroup().StartTime()}} {}
 
 LocalInstance::Copy::MockParent::MockParent(const MockParentParam& params)
     : home_dir_{params.home_dir},
       host_artifacts_path_{params.host_artifacts_path},
       internal_group_name_{params.internal_group_name},
-      group_name_{params.group_name} {}
+      group_name_{params.group_name},
+      start_time_{params.start_time} {}
 
 }  // namespace selector
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/selector/instance_record.h b/host/commands/cvd/selector/instance_record.h
index f6bdf9a..db7e690 100644
--- a/host/commands/cvd/selector/instance_record.h
+++ b/host/commands/cvd/selector/instance_record.h
@@ -16,10 +16,10 @@
 
 #pragma once
 
-#include <optional>
 #include <string>
 
 #include "common/libs/utils/result.h"
+#include "host/commands/cvd/selector/instance_database_types.h"
 
 namespace cuttlefish {
 namespace selector {
@@ -36,6 +36,9 @@
   friend class InstanceDatabase;
 
  public:
+  static constexpr const char kJsonInstanceId[] = "Instance Id";
+  static constexpr const char kJsonInstanceName[] = "Per-Instance Name";
+
   /* names:
    *
    * Many components in Cuttlefish traditionally expect the name to be "cvd-N,"
@@ -61,6 +64,7 @@
       std::string host_artifacts_path;
       std::string internal_group_name;
       std::string group_name;
+      TimeStamp start_time;
     };
 
    public:
@@ -79,12 +83,14 @@
       const std::string& HostArtifactsPath() const {
         return host_artifacts_path_;
       }
+      auto StartTime() const { return start_time_; }
 
      private:
       std::string home_dir_;
       std::string host_artifacts_path_;
       std::string internal_group_name_;
       std::string group_name_;
+      TimeStamp start_time_;
     };
     Copy(const LocalInstance& src);
     const std::string& InternalName() const { return internal_name_; }
@@ -110,9 +116,6 @@
   LocalInstance(const LocalInstanceGroup& parent_group,
                 const unsigned instance_id, const std::string& instance_name);
 
-  static constexpr const char kJsonInstanceId[] = "Instance Id";
-  static constexpr const char kJsonInstanceName[] = "Per-Instance Name";
-
   const LocalInstanceGroup& parent_group_;
   unsigned instance_id_;
   std::string internal_name_;  ///< for now, it is to_string(instance_id_)
diff --git a/host/commands/cvd/server.cc b/host/commands/cvd/server.cc
index 87e427d..540ebe6 100644
--- a/host/commands/cvd/server.cc
+++ b/host/commands/cvd/server.cc
@@ -283,8 +283,9 @@
   if (!response.ok()) {
     cvd::Response failure_message;
     failure_message.mutable_status()->set_code(cvd::Status::INTERNAL);
+    const bool color = request->Err()->IsOpen() && request->Err()->IsATTY();
     failure_message.mutable_status()->set_message(
-        response.error().FormatForEnv());
+        response.error().FormatForEnv(color));
     CF_EXPECT(SendResponse(event.fd, failure_message));
     return {};  // Error already sent to the client, don't repeat on the server
   }
diff --git a/host/commands/cvd/server_command/Android.bp b/host/commands/cvd/server_command/Android.bp
index 575d742..0ae8c7f 100644
--- a/host/commands/cvd/server_command/Android.bp
+++ b/host/commands/cvd/server_command/Android.bp
@@ -36,6 +36,7 @@
         "generic.cpp",
         "handler_proxy.cpp",
         "help.cpp",
+        "lint.cpp",
         "load_configs.cpp",
         "power.cpp",
         "reset.cpp",
diff --git a/host/commands/cvd/server_command/fetch.cpp b/host/commands/cvd/server_command/fetch.cpp
index c258c3f..cb86c01 100644
--- a/host/commands/cvd/server_command/fetch.cpp
+++ b/host/commands/cvd/server_command/fetch.cpp
@@ -16,10 +16,13 @@
 
 #include "host/commands/cvd/server_command/fetch.h"
 
+#include <android-base/strings.h>
+
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/contains.h"
 #include "common/libs/utils/result.h"
+#include "common/libs/utils/subprocess.h"
 #include "host/commands/cvd/server_command/server_handler.h"
 #include "host/commands/cvd/server_command/utils.h"
 #include "host/commands/cvd/types.h"
@@ -36,6 +39,9 @@
   Result<cvd::Response> Handle(const RequestWithStdio& request) override;
   Result<void> Interrupt() override;
   cvd_common::Args CmdList() const override { return fetch_cmd_list_; }
+  Result<std::string> SummaryHelp() const override;
+  virtual bool ShouldInterceptHelp() const { return true; }
+  Result<std::string> DetailedHelp(std::vector<std::string>&) const override;
 
  private:
   SubprocessWaiter& subprocess_waiter_;
@@ -107,6 +113,22 @@
   return {};
 }
 
+Result<std::string> CvdFetchCommandHandler::SummaryHelp() const {
+  return "Retrieve build artifacts based on branch and target names";
+}
+
+Result<std::string> CvdFetchCommandHandler::DetailedHelp(
+    std::vector<std::string>&) const {
+  Command fetch_command("/proc/self/exe");
+  fetch_command.SetName("fetch_cvd");
+  fetch_command.SetExecutable("/proc/self/exe");
+  fetch_command.AddParameter("--help");
+
+  std::string output;
+  RunWithManagedStdio(std::move(fetch_command), nullptr, nullptr, &output);
+  return output;
+}
+
 std::unique_ptr<CvdServerHandler> NewCvdFetchCommandHandler(
     SubprocessWaiter& subprocess_waiter) {
   return std::unique_ptr<CvdServerHandler>(
diff --git a/host/commands/cvd/server_command/fleet.cpp b/host/commands/cvd/server_command/fleet.cpp
index 12c52e3..c1d5d27 100644
--- a/host/commands/cvd/server_command/fleet.cpp
+++ b/host/commands/cvd/server_command/fleet.cpp
@@ -28,6 +28,8 @@
 #include "common/libs/utils/files.h"
 #include "common/libs/utils/json.h"
 #include "common/libs/utils/result.h"
+#include "host/commands/cvd/selector/instance_database_types.h"
+#include "host/commands/cvd/selector/selector_constants.h"
 #include "host/commands/cvd/server_client.h"
 #include "host/commands/cvd/server_command/server_handler.h"
 #include "host/commands/cvd/server_command/status_fetcher.h"
@@ -110,8 +112,19 @@
   auto all_group_names = instance_manager_.AllGroupNames(uid);
   envs.erase(kCuttlefishInstanceEnvVarName);
   for (const auto& group_name : all_group_names) {
+    auto group_obj_copy_result = instance_manager_.SelectGroup(
+        {}, InstanceManager::Queries{{selector::kGroupNameField, group_name}},
+        {}, uid);
+    if (!group_obj_copy_result.ok()) {
+      LOG(DEBUG) << "Group \"" << group_name
+                 << "\" has already been removed. Skipped.";
+      continue;
+    }
+
     Json::Value group_json(Json::objectValue);
     group_json["group_name"] = group_name;
+    group_json["start_time"] =
+        selector::Format(group_obj_copy_result->StartTime());
 
     auto request_message = MakeRequest(
         {.cmd_args = {"cvd", "status", "--print", "--all_instances"},
diff --git a/host/commands/cvd/server_command/generic.cpp b/host/commands/cvd/server_command/generic.cpp
index 5ffc2b5..79ec7fe 100644
--- a/host/commands/cvd/server_command/generic.cpp
+++ b/host/commands/cvd/server_command/generic.cpp
@@ -22,6 +22,7 @@
 #include <mutex>
 
 #include <android-base/file.h>
+#include <android-base/parseint.h>
 #include <android-base/scopeguard.h>
 
 #include "common/libs/fs/shared_buf.h"
@@ -35,6 +36,7 @@
 #include "host/commands/cvd/command_sequence.h"
 #include "host/commands/cvd/common_utils.h"
 #include "host/commands/cvd/instance_manager.h"
+#include "host/commands/cvd/interruptible_terminal.h"
 #include "host/commands/cvd/selector/selector_constants.h"
 #include "host/commands/cvd/server_command/host_tool_target_manager.h"
 #include "host/commands/cvd/server_command/server_handler.h"
@@ -67,12 +69,19 @@
     std::vector<std::string> args;
     cvd_common::Envs envs;
   };
+  enum class UiResponseType : int {
+    kNoGroup = 1,        // no group is active
+    kNoTTY = 2,          // there are groups to select but no tty for user input
+    kUserSelection = 3,  // selector couldn't pick so asked the user
+    kCvdServerPick = 4,  // selector picks based on selector flags, env, etc
+  };
   struct ExtractedInfo {
     CommandInvocationInfo invocation_info;
     std::optional<selector::LocalInstanceGroup> group;
     bool is_non_help_cvd;
+    UiResponseType ui_response_type;
   };
-  Result<ExtractedInfo> ExtractInfo(const RequestWithStdio& request) const;
+  Result<ExtractedInfo> ExtractInfo(const RequestWithStdio& request);
   Result<std::string> GetBin(const std::string& subcmd) const;
   Result<std::string> GetBin(const std::string& subcmd,
                              const std::string& host_artifacts_path) const;
@@ -100,6 +109,7 @@
   using BinGeneratorType = std::function<Result<std::string>(
       const std::string& host_artifacts_path)>;
   std::map<std::string, std::string> command_to_binary_map_;
+  std::unique_ptr<InterruptibleTerminal> terminal_ = nullptr;
 
   static constexpr char kHostBugreportBin[] = "cvd_internal_host_bugreport";
   static constexpr char kLnBin[] = "ln";
@@ -134,6 +144,13 @@
 Result<void> CvdGenericCommandHandler::Interrupt() {
   std::scoped_lock interrupt_lock(interruptible_);
   interrupted_ = true;
+  if (terminal_) {
+    auto terminal_interrupt_result = terminal_->Interrupt();
+    // TODO(b/316202887): utilize the multi-CF_EXPECT feature
+    if (!terminal_interrupt_result.ok()) {
+      LOG(ERROR) << "Failed to interrupt terminal";
+    }
+  }
   CF_EXPECT(subprocess_waiter_.Interrupt());
   return {};
 }
@@ -141,9 +158,7 @@
 Result<cvd::Response> CvdGenericCommandHandler::Handle(
     const RequestWithStdio& request) {
   std::unique_lock interrupt_lock(interruptible_);
-  if (interrupted_) {
-    return CF_ERR("Interrupted");
-  }
+  CF_EXPECT(!interrupted_, "Interrupted");
   CF_EXPECT(CanHandle(request));
   CF_EXPECT(request.Credentials() != std::nullopt);
   const uid_t uid = request.Credentials()->uid;
@@ -158,17 +173,26 @@
         precondition_verified.error().Message());
     return response;
   }
-  auto [invocation_info, group_opt, is_non_help_cvd] =
+
+  interrupt_lock.unlock();
+  auto [invocation_info, group_opt, is_non_help_cvd, ui_response_type] =
       CF_EXPECT(ExtractInfo(request));
+
+  interrupt_lock.lock();
+  CF_EXPECT(!interrupted_, "Interrupted");
   if (invocation_info.bin == kClearBin) {
     *response.mutable_status() =
         instance_manager_.CvdClear(request.Out(), request.Err());
     return response;
   }
 
-  if (is_non_help_cvd && !group_opt) {
+  // besides the two cases, the rest will be handled by running subprocesses
+  if (is_non_help_cvd && ui_response_type == UiResponseType::kNoGroup) {
     return CF_EXPECT(NoGroupResponse(request));
   }
+  if (is_non_help_cvd && ui_response_type == UiResponseType::kNoTTY) {
+    return CF_EXPECT(NoTTYResponse(request));
+  }
 
   ConstructCommandParam construct_cmd_param{
       .bin_path = invocation_info.bin_path,
@@ -278,7 +302,7 @@
  *
  */
 Result<CvdGenericCommandHandler::ExtractedInfo>
-CvdGenericCommandHandler::ExtractInfo(const RequestWithStdio& request) const {
+CvdGenericCommandHandler::ExtractInfo(const RequestWithStdio& request) {
   auto result_opt = request.Credentials();
   CF_EXPECT(result_opt != std::nullopt);
   const uid_t uid = result_opt->uid;
@@ -311,18 +335,87 @@
                 .args = cmd_args,
                 .envs = envs},
         .group = std::nullopt,
-        .is_non_help_cvd = false};
+        .is_non_help_cvd = false,
+        .ui_response_type = UiResponseType::kCvdServerPick,
+    };
   }
 
   auto instance_group_result =
       instance_manager_.SelectGroup(selector_args, envs, uid);
-  ExtractedInfo extracted_info;
-  extracted_info.is_non_help_cvd = true;
+  ExtractedInfo extracted_info{
+      .invocation_info = CommandInvocationInfo(),
+      .group = std::nullopt,
+      .is_non_help_cvd = true,
+      .ui_response_type = UiResponseType::kCvdServerPick,
+  };
+  std::string chosen_group_name;
   if (!instance_group_result.ok()) {
-    CF_EXPECT(!instance_manager_.HasInstanceGroups(uid),
-              instance_group_result.error().FormatForEnv());
-    return extracted_info;
+    if (!instance_manager_.HasInstanceGroups(uid)) {
+      extracted_info.ui_response_type = UiResponseType::kNoGroup;
+      return extracted_info;
+    }
+
+    if (!request.In()->IsOpen() || !request.In()->IsATTY()) {
+      // can't take the user input
+      extracted_info.ui_response_type = UiResponseType::kNoTTY;
+      return extracted_info;
+    }
+
+    extracted_info.ui_response_type = UiResponseType::kUserSelection;
+    std::unique_lock lock(interruptible_);
+    CF_EXPECT(!interrupted_, "Interrupted");
+    // show the menu and let the user choose
+    auto group_summaries = CF_EXPECT(instance_manager_.GroupSummaryMenu(uid));
+    auto& group_summary_menu = group_summaries.menu;
+    CF_EXPECT_EQ(WriteAll(request.Out(), group_summary_menu + "\n"),
+                 group_summary_menu.size() + 1);
+    terminal_ = std::make_unique<InterruptibleTerminal>(request.In());
+    lock.unlock();
+
+    const bool is_tty = request.Err()->IsOpen() && request.Err()->IsATTY();
+    while (true) {
+      lock.lock();
+      std::string question = fmt::format(
+          "For which instance group would you like to run {}? ", subcmd);
+      CF_EXPECT_EQ(WriteAll(request.Out(), question), question.size());
+      lock.unlock();
+
+      std::string input_line = CF_EXPECT(terminal_->ReadLine());
+      int selection = -1;
+      if (android::base::ParseInt(input_line, &selection)) {
+        const auto n_groups = group_summaries.idx_to_group_name.size();
+        if (n_groups <= selection || selection < 0) {
+          std::string out_of_range = fmt::format(
+              "\n  Selection {}{}{} is beyond the range {}[0, {}]{}\n\n",
+              TerminalColor(is_tty, TerminalColors::kBoldRed), selection,
+              TerminalColor(is_tty, TerminalColors::kReset),
+              TerminalColor(is_tty, TerminalColors::kCyan), n_groups - 1,
+              TerminalColor(is_tty, TerminalColors::kReset));
+          CF_EXPECT_EQ(WriteAll(request.Err(), out_of_range),
+                       out_of_range.size());
+          continue;
+        }
+        chosen_group_name = group_summaries.idx_to_group_name[selection];
+      } else {
+        chosen_group_name = android::base::Trim(input_line);
+      }
+
+      InstanceManager::Queries extra_queries{
+          {selector::kGroupNameField, chosen_group_name}};
+      instance_group_result = instance_manager_.SelectGroup(
+          selector_args, extra_queries, envs, uid);
+      if (instance_group_result.ok()) {
+        break;
+      }
+      std::string cannot_find_group_name = fmt::format(
+          "\n  Failed to find a group whose name is {}\"{}\"{}\n\n",
+          TerminalColor(is_tty, TerminalColors::kBoldRed), chosen_group_name,
+          TerminalColor(is_tty, TerminalColors::kReset));
+      CF_EXPECT_EQ(WriteAll(request.Err(), cannot_find_group_name),
+                   cannot_find_group_name.size());
+    }
   }
+
   auto& instance_group = *instance_group_result;
   auto android_host_out = instance_group.HostArtifactsPath();
   auto home = instance_group.HomeDir();
diff --git a/host/commands/cvd/server_command/handler_proxy.cpp b/host/commands/cvd/server_command/handler_proxy.cpp
index 7d6bc78..7747ac6 100644
--- a/host/commands/cvd/server_command/handler_proxy.cpp
+++ b/host/commands/cvd/server_command/handler_proxy.cpp
@@ -97,11 +97,20 @@
     interrupt_lock.unlock();
     SharedFD dev_null = SharedFD::Open("/dev/null", O_RDWR);
     CF_EXPECT(dev_null->IsOpen(), "Failed to open /dev/null");
-    const auto responses =
-        CF_EXPECT(executor_.Execute({std::move(forwarded_request)}, dev_null));
-    CF_EXPECT_EQ(responses.size(), 1);
-    // TODO(moelsherif): check the response for failed command
-    return responses.front();
+
+    cvd::Response response;
+    auto invocation_args =
+        ParseInvocation(forwarded_request.Message()).arguments;
+    auto handler = CF_EXPECT(executor_.GetHandler(forwarded_request));
+    if (CF_EXPECT(IsHelpSubcmd(invocation_args)) &&
+        handler->ShouldInterceptHelp()) {
+      std::string output =
+          CF_EXPECT(handler->DetailedHelp(invocation_args)) + "\n";
+      response = CF_EXPECT(WriteToFd(forwarded_request.Out(), output));
+    } else {
+      response = CF_EXPECT(executor_.ExecuteOne(forwarded_request, dev_null));
+    }
+    return response;
   }
 
   Result<void> Interrupt() override {
diff --git a/host/commands/cvd/server_command/help.cpp b/host/commands/cvd/server_command/help.cpp
index b98303a..dad5b32 100644
--- a/host/commands/cvd/server_command/help.cpp
+++ b/host/commands/cvd/server_command/help.cpp
@@ -16,17 +16,31 @@
 
 #include "host/commands/cvd/server_command/help.h"
 
+#include <fcntl.h>
+
+#include <memory>
 #include <mutex>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <android-base/strings.h>
 
 #include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+#include "cvd_server.pb.h"
 #include "host/commands/cvd/command_sequence.h"
+#include "host/commands/cvd/request_context.h"
 #include "host/commands/cvd/server.h"
 #include "host/commands/cvd/server_command/utils.h"
 #include "host/commands/cvd/types.h"
 
 namespace cuttlefish {
+namespace {
 
-static constexpr char kHelpMessage[] = R"(Cuttlefish Virtual Device (CVD) CLI.
+constexpr char kHelpIntroText[] = R"(Cuttlefish Virtual Device (CVD) CLI.
 
 usage: cvd <selector/driver options> <command> <args>
 
@@ -45,39 +59,27 @@
   -acquire_file_lock     If the flag is given, the cvd server attempts to
                          acquire the instance lock file lock. (default: true)
 
-Commands:
-  help                   Print this message.
-  help <command>         Print help for a command.
-  start                  Start a device.
-  stop                   Stop a running device.
-  clear                  Stop all running devices and delete all instance and
-                         assembly directories.
-  fleet                  View the current fleet status.
-  kill-server            Kill the cvd_server background process.
-  server-kill            Same as kill-server
-  powerwash              Delivers powerwash command to the selected device
-  restart                Restart the device without reinitializing the disks
-  restart-server         Restart the cvd_server background process.
-  status                 Check and print the state of a running instance.
-  env                    Control the environment of running instances like wifi
-                         or geolocation.
-  host_bugreport         Capture a host bugreport, including configs, logs, and
-                         tombstones.
+Commands (cvd help <command> for more information):)";
 
-Args:
-  <command args>         Each command has its own set of args.
-                         See cvd help <command>.
+constexpr char kSummaryHelpText[] =
+    "Used to display help information for other commands";
 
-Experimental:
-  reset                  See cvd reset --help. Requires cvd >= v1.2
-  snapshot_take          cvd snapshot_take --help. Requires crosvm, cvd >= v1.4
-  suspend                cvd suspend --help. Requires crosvm, cvd >= v1.4
-  resume                 cvd resume --help. Requires crosvm, cvd >= v1.4
+constexpr char kDetailedHelpText[] =
+    R"(cvd help - used to display help text for cvd and its commands
+
+Example usage:
+  cvd help - displays summary help for available commands
+
+  cvd help <command> - displays more detailed help for the specific command
 )";
 
+}  // namespace
+
 class CvdHelpHandler : public CvdServerHandler {
  public:
-  CvdHelpHandler(CommandSequenceExecutor& executor) : executor_(executor) {}
+  CvdHelpHandler(
+      const std::vector<std::unique_ptr<CvdServerHandler>>& request_handlers)
+      : request_handlers_(request_handlers) {}
 
   Result<bool> CanHandle(const RequestWithStdio& request) const override {
     auto invocation = ParseInvocation(request.Message());
@@ -89,81 +91,85 @@
     if (interrupted_) {
       return CF_ERR("Interrupted");
     }
-
-    cvd::Response response;
-    response.mutable_command_response();  // Sets oneof member
-    response.mutable_status()->set_code(cvd::Status::OK);
-
     CF_EXPECT(CanHandle(request));
 
-    auto [subcmd, subcmd_args] = ParseInvocation(request.Message());
-    const auto supported_subcmd_list = executor_.CmdList();
-
-    /*
-     * cvd help, cvd help invalid_token, cvd help help
-     */
-    if (subcmd_args.empty() ||
-        !Contains(supported_subcmd_list, subcmd_args.front()) ||
-        subcmd_args.front() == "help") {
-      WriteAll(request.Out(), kHelpMessage);
-      return response;
+    std::string output;
+    auto args = ParseInvocation(request.Message()).arguments;
+    if (args.empty()) {
+      output = CF_EXPECT(TopLevelHelp());
+    } else {
+      output = CF_EXPECT(SubCommandHelp(args));
     }
-
-    cvd::Request modified_proto = HelpSubcommandToFlag(request);
-
-    RequestWithStdio inner_cmd(request.Client(), modified_proto,
-                               request.FileDescriptors(),
-                               request.Credentials());
-
+    auto response = CF_EXPECT(WriteToFd(request.Out(), output));
     interrupt_lock.unlock();
-    CF_EXPECT(
-        executor_.Execute({inner_cmd}, SharedFD::Open("/dev/null", O_RDWR)));
-
     return response;
   }
 
   Result<void> Interrupt() override {
     std::scoped_lock interrupt_lock(interruptible_);
     interrupted_ = true;
-    CF_EXPECT(executor_.Interrupt());
     return {};
   }
 
   cvd_common::Args CmdList() const override { return {"help"}; }
 
+  Result<std::string> SummaryHelp() const override { return kSummaryHelpText; }
+
+  bool ShouldInterceptHelp() const override { return true; }
+
+  Result<std::string> DetailedHelp(std::vector<std::string>&) const override {
+    return kDetailedHelpText;
+  }
+
  private:
-  cvd::Request HelpSubcommandToFlag(const RequestWithStdio& request);
+  Result<RequestWithStdio> GetLookupRequest(const std::string& arg) {
+    cvd::Request lookup;
+    auto& lookup_cmd = *lookup.mutable_command_request();
+    lookup_cmd.add_args("cvd");
+    lookup_cmd.add_args(arg);
+    auto dev_null = SharedFD::Open("/dev/null", O_RDWR);
+    CF_EXPECT(dev_null->IsOpen(), dev_null->StrError());
+    return RequestWithStdio(dev_null, lookup, {dev_null, dev_null, dev_null},
+                            {});
+  }
+
+  Result<std::string> TopLevelHelp() {
+    std::stringstream help_message;
+    help_message << kHelpIntroText << std::endl;
+    for (const auto& handler : request_handlers_) {
+      std::string command_list = android::base::Join(handler->CmdList(), ", ");
+      // exclude commands without any command list values as not intended for
+      // use by users or sub-subcommands
+      if (!command_list.empty()) {
+        help_message << "\t" << command_list << " - ";
+        help_message << CF_EXPECT(handler->SummaryHelp()) << std::endl
+                     << std::endl;
+      }
+    }
+    return help_message.str();
+  }
+
+  Result<std::string> SubCommandHelp(std::vector<std::string>& args) {
+    CF_EXPECT(
+        !args.empty(),
+        "Cannot process subcommand help without valid subcommand argument");
+    auto lookup_request = CF_EXPECT(GetLookupRequest(args.front()));
+    auto handler = CF_EXPECT(RequestHandler(lookup_request, request_handlers_));
+
+    std::stringstream help_message;
+    help_message << CF_EXPECT(handler->DetailedHelp(args)) << std::endl;
+    return help_message.str();
+  }
 
   std::mutex interruptible_;
   bool interrupted_ = false;
-  CommandSequenceExecutor& executor_;
+  const std::vector<std::unique_ptr<CvdServerHandler>>& request_handlers_;
 };
 
-cvd::Request CvdHelpHandler::HelpSubcommandToFlag(
-    const RequestWithStdio& request) {
-  cvd::Request modified_proto = request.Message();
-  auto all_args =
-      cvd_common::ConvertToArgs(modified_proto.command_request().args());
-  auto& args = *modified_proto.mutable_command_request()->mutable_args();
-  args.Clear();
-  // there must be one or more "help" in all_args
-  // delete the first "help"
-  bool found_help = false;
-  for (const auto& cmd_arg : all_args) {
-    if (cmd_arg != "help" || found_help) {
-      args.Add(cmd_arg.c_str());
-      continue;
-    }
-    // skip first help
-    found_help = true;
-  }
-  args.Add("--help");
-  return modified_proto;
-}
-
 std::unique_ptr<CvdServerHandler> NewCvdHelpHandler(
-    CommandSequenceExecutor& executor) {
-  return std::unique_ptr<CvdServerHandler>(new CvdHelpHandler(executor));
+    const std::vector<std::unique_ptr<CvdServerHandler>>& request_handlers) {
+  return std::unique_ptr<CvdServerHandler>(
+      new CvdHelpHandler(request_handlers));
 }
 
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/server_command/help.h b/host/commands/cvd/server_command/help.h
index 28014b2..38085b3 100644
--- a/host/commands/cvd/server_command/help.h
+++ b/host/commands/cvd/server_command/help.h
@@ -17,13 +17,12 @@
 #pragma once
 
 #include <memory>
+#include <vector>
 
-#include "host/commands/cvd/command_sequence.h"
 #include "host/commands/cvd/server_command/server_handler.h"
 
 namespace cuttlefish {
 
 std::unique_ptr<CvdServerHandler> NewCvdHelpHandler(
-    CommandSequenceExecutor& executor);
-
+    const std::vector<std::unique_ptr<CvdServerHandler>>& server_handlers);
 }
diff --git a/host/commands/cvd/server_command/host_tool_target_manager.cpp b/host/commands/cvd/server_command/host_tool_target_manager.cpp
index 9f85b8f..b8ceb6b 100644
--- a/host/commands/cvd/server_command/host_tool_target_manager.cpp
+++ b/host/commands/cvd/server_command/host_tool_target_manager.cpp
@@ -63,7 +63,7 @@
   if (!host_target.IsDirty()) {
     return {};
   }
-  LOG(ERROR) << artifacts_path << " is new, so updating HostToolTarget";
+  LOG(INFO) << artifacts_path << " is new, so updating HostToolTarget";
   host_target_table_.erase(artifacts_path);
   HostToolTarget new_host_tool_target =
       CF_EXPECT(HostToolTarget::Create(artifacts_path));
diff --git a/host/commands/cvd/server_command/lint.cpp b/host/commands/cvd/server_command/lint.cpp
new file mode 100644
index 0000000..716f0ca
--- /dev/null
+++ b/host/commands/cvd/server_command/lint.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/cvd/server_command/lint.h"
+
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <json/json.h>
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/utils/result.h"
+#include "host/commands/cvd/command_sequence.h"
+#include "host/commands/cvd/common_utils.h"
+#include "host/commands/cvd/parser/load_configs_parser.h"
+#include "host/commands/cvd/selector/selector_constants.h"
+#include "host/commands/cvd/server_client.h"
+#include "host/commands/cvd/server_command/server_handler.h"
+#include "host/commands/cvd/server_command/utils.h"
+#include "host/commands/cvd/types.h"
+
+namespace cuttlefish {
+
+class LintCommandHandler : public CvdServerHandler {
+ public:
+  LintCommandHandler() {}
+
+  Result<bool> CanHandle(const RequestWithStdio& request) const {
+    auto invocation = ParseInvocation(request.Message());
+    return invocation.command == kLintSubCmd;
+  }
+
+  Result<cvd::Response> Handle(const RequestWithStdio& request) override {
+    CF_EXPECT(CanHandle(request));
+
+    auto args = ParseInvocation(request.Message()).arguments;
+    auto working_directory =
+        request.Message().command_request().working_directory();
+    const auto config_path = CF_EXPECT(ValidateConfig(args, working_directory));
+
+    std::stringstream message_stream;
+    message_stream << "Lint of flags and config \"" << config_path
+                   << "\" succeeded\n";
+    const auto message = message_stream.str();
+    CF_EXPECT_EQ(WriteAll(request.Out(), message), message.size(),
+                 "Error writing message");
+    cvd::Response response;
+    response.mutable_command_response();
+    response.mutable_status()->set_code(cvd::Status::OK);
+    return response;
+  }
+
+  Result<void> Interrupt() override { return CF_ERR("Can't interrupt"); }
+
+  cvd_common::Args CmdList() const override { return {kLintSubCmd}; }
+
+ private:
+  Result<std::string> ValidateConfig(std::vector<std::string>& args,
+                                     const std::string& working_directory) {
+    const LoadFlags flags = CF_EXPECT(GetFlags(args, working_directory));
+    CF_EXPECT(GetCvdFlags(flags));
+    return flags.config_path;
+  }
+
+  static constexpr char kLintSubCmd[] = "lint";
+};
+
+std::unique_ptr<CvdServerHandler> NewLintCommand() {
+  return std::unique_ptr<CvdServerHandler>(new LintCommandHandler());
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/server_command/lint.h b/host/commands/cvd/server_command/lint.h
new file mode 100644
index 0000000..a860e49
--- /dev/null
+++ b/host/commands/cvd/server_command/lint.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <memory>
+
+#include "host/commands/cvd/server_command/server_handler.h"
+
+namespace cuttlefish {
+
+std::unique_ptr<CvdServerHandler> NewLintCommand();
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/server_command/load_configs.cpp b/host/commands/cvd/server_command/load_configs.cpp
index 2863b9a..2707b60 100644
--- a/host/commands/cvd/server_command/load_configs.cpp
+++ b/host/commands/cvd/server_command/load_configs.cpp
@@ -35,6 +35,25 @@
 #include "host/commands/cvd/types.h"
 
 namespace cuttlefish {
+namespace {
+
+constexpr char kSummaryHelpText[] =
+    R"(Loads the given JSON configuration file and launches devices based on the options provided)";
+
+constexpr char kDetailedHelpText[] = R"(
+Warning: This command is deprecated, use cvd start --config_file instead.
+
+Usage:
+cvd load <config_filepath> [--override=<key>:<value>]
+
+Reads the fields in the JSON configuration file and translates them to corresponding start command and flags.  
+
+Optionally fetches remote artifacts prior to launching the cuttlefish environment.
+
+The --override flag can be used to give new values for properties in the config file without needing to edit the file directly.  Convenient for one-off invocations.
+)";
+
+}  // namespace
 
 class LoadConfigsCommand : public CvdServerHandler {
  public:
@@ -69,40 +88,24 @@
 
   cvd_common::Args CmdList() const override { return {kLoadSubCmd}; }
 
+  Result<std::string> SummaryHelp() const override { return kSummaryHelpText; }
+
+  bool ShouldInterceptHelp() const override { return true; }
+
+  Result<std::string> DetailedHelp(std::vector<std::string>&) const override {
+    return kDetailedHelpText;
+  }
+
   Result<std::vector<RequestWithStdio>> CreateCommandSequence(
       const RequestWithStdio& request) {
     auto args = ParseInvocation(request.Message()).arguments;
     auto working_directory =
         request.Message().command_request().working_directory();
     const LoadFlags flags = CF_EXPECT(GetFlags(args, working_directory));
-
-    if (flags.help) {
-      std::stringstream help_msg_stream;
-      help_msg_stream << "Usage: cvd " << kLoadSubCmd << "\n";
-      const auto help_msg = help_msg_stream.str();
-      CF_EXPECT(WriteAll(request.Out(), help_msg) == help_msg.size());
-      return {};
-    }
-
-    Json::Value json_configs =
-        CF_EXPECT(GetOverriddenConfig(flags.config_path, flags.overrides));
-
-    Result<std::vector<std::string>> system_image_path_configs =
-        CF_EXPECT(GetConfiguredSystemImagePaths(json_configs));
-
-    std::optional<std::string> host_package_dir =
-        GetConfiguredSystemHostPath(json_configs);
-
-    const auto& client_env = request.Message().command_request().env();
-
-    const auto load_directories = CF_EXPECT(GenerateLoadDirectories(
-        flags.base_dir, *system_image_path_configs, host_package_dir,
-        json_configs["instances"].size()));
-
-    auto cvd_flags = CF_EXPECT(ParseCvdConfigs(json_configs, load_directories),
-                               "parsing json configs failed");
+    auto cvd_flags = CF_EXPECT(GetCvdFlags(flags));
 
     std::vector<cvd::Request> req_protos;
+    const auto& client_env = request.Message().command_request().env();
 
     if (!cvd_flags.fetch_cvd_flags.empty()) {
       auto& fetch_cmd = *req_protos.emplace_back().mutable_command_request();
@@ -119,17 +122,18 @@
     mkdir_cmd.add_args("cvd");
     mkdir_cmd.add_args("mkdir");
     mkdir_cmd.add_args("-p");
-    mkdir_cmd.add_args(load_directories.launch_home_directory);
+    mkdir_cmd.add_args(cvd_flags.load_directories.launch_home_directory);
 
     auto& launch_cmd = *req_protos.emplace_back().mutable_command_request();
-    launch_cmd.set_working_directory(load_directories.host_package_directory);
+    launch_cmd.set_working_directory(
+        cvd_flags.load_directories.host_package_directory);
     *launch_cmd.mutable_env() = client_env;
     (*launch_cmd.mutable_env())["HOME"] =
-        load_directories.launch_home_directory;
+        cvd_flags.load_directories.launch_home_directory;
     (*launch_cmd.mutable_env())[kAndroidHostOut] =
-        load_directories.host_package_directory;
+        cvd_flags.load_directories.host_package_directory;
     (*launch_cmd.mutable_env())[kAndroidSoongHostOut] =
-        load_directories.host_package_directory;
+        cvd_flags.load_directories.host_package_directory;
     if (Contains(*launch_cmd.mutable_env(), kAndroidProductOut)) {
       (*launch_cmd.mutable_env()).erase(kAndroidProductOut);
     }
@@ -145,7 +149,7 @@
       launch_cmd.add_args(parsed_flag);
     }
     // Add system flag for multi-build scenario
-    launch_cmd.add_args(load_directories.system_image_directory_flag);
+    launch_cmd.add_args(cvd_flags.load_directories.system_image_directory_flag);
 
     auto selector_opts = launch_cmd.mutable_selector_opts();
 
diff --git a/host/commands/cvd/server_command/server_handler.h b/host/commands/cvd/server_command/server_handler.h
index 4d327f5..ec523fa 100644
--- a/host/commands/cvd/server_command/server_handler.h
+++ b/host/commands/cvd/server_command/server_handler.h
@@ -16,6 +16,9 @@
 
 #pragma once
 
+#include <string>
+#include <vector>
+
 #include "common/libs/utils/result.h"
 #include "host/commands/cvd/server_client.h"
 #include "host/commands/cvd/types.h"
@@ -31,6 +34,14 @@
   virtual Result<void> Interrupt() = 0;
   // returns the list of subcommand it can handle
   virtual cvd_common::Args CmdList() const = 0;
+  // TODO make pure virtual once every implementation has overrides
+  virtual Result<std::string> SummaryHelp() const {
+    return "Consider contributing a CL with help text if you read this :)";
+  }
+  virtual bool ShouldInterceptHelp() const { return false; }
+  virtual Result<std::string> DetailedHelp(std::vector<std::string>&) const {
+    return "Consider contributing a CL with help text if you read this :)";
+  }
 };
 
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/server_command/start.cpp b/host/commands/cvd/server_command/start.cpp
index 871301c..6ca849b 100644
--- a/host/commands/cvd/server_command/start.cpp
+++ b/host/commands/cvd/server_command/start.cpp
@@ -18,6 +18,7 @@
 
 #include <sys/types.h>
 
+#include <algorithm>
 #include <array>
 #include <atomic>
 #include <cstdint>
@@ -81,6 +82,23 @@
                           request.FileDescriptors(), request.Credentials());
 }
 
+// link might be a directory, so we clean that up, and create a link from
+// target to link
+Result<void> EnsureSymlink(const std::string& target, const std::string link) {
+  if (DirectoryExists(link, /* follow_symlinks */ false)) {
+    CF_EXPECTF(RecursivelyRemoveDirectory(link),
+               "Failed to remove legacy directory \"{}\"", link);
+  }
+  if (FileExists(link, /* follow_symlinks */ false)) {
+    CF_EXPECTF(RemoveFile(link), "Failed to remove file \"{}\": {}", link,
+               std::strerror(errno));
+  }
+  CF_EXPECTF(symlink(target.c_str(), link.c_str()) == 0,
+             "symlink(\"{}\", \"{}\") failed: {}", target, link,
+             std::strerror(errno));
+  return {};
+}
+
 }  // namespace
 
 class CvdStartCommandHandler : public CvdServerHandler {
@@ -470,7 +488,7 @@
     }
   }
   ss << " " << bin << " " << args;
-  LOG(ERROR) << "launcher command: " << ss.str();
+  LOG(INFO) << "launcher command: " << ss.str();
 }
 
 static void ShowLaunchCommand(const std::string& bin,
@@ -527,63 +545,33 @@
 // when HOME is NOT overridden and selector flags are NOT given.
 Result<void> CvdStartCommandHandler::CreateSymlinks(
     const selector::GroupCreationInfo& group_creation_info) {
-  std::string instance_home_dir = "";
+  CF_EXPECT(EnsureDirectoryExists(group_creation_info.home));
   auto system_wide_home = CF_EXPECT(SystemWideUserHome());
+  auto smallest_id = std::numeric_limits<unsigned>::max();
   for (const auto& instance : group_creation_info.instances) {
-    std::string legacy_path = system_wide_home + "/cuttlefish_runtime.";
-    legacy_path = ConcatToString(legacy_path, instance.instance_id_);
-    instance_home_dir = group_creation_info.home;
-    instance_home_dir = instance_home_dir + "/cuttlefish/instances/cvd-";
-    instance_home_dir = ConcatToString(instance_home_dir, instance.instance_id_);
-    if (DirectoryExists(legacy_path, /* follow_symlinks */ true)) {
-      CF_EXPECT(RecursivelyRemoveDirectory(legacy_path),
-                "Failed to remove legacy directory " << legacy_path);
-    }
-    if (symlink(instance_home_dir.c_str(), legacy_path.c_str())) {
-      return CF_ERRNO("symlink(\"" << instance_home_dir << "\", \""
-                                   << legacy_path << "\") failed");
-    }
-    legacy_path = system_wide_home + "/cuttlefish_runtime";
-    if (DirectoryExists(legacy_path, true)) {
-      CF_EXPECT(RecursivelyRemoveDirectory(legacy_path),
-                "Failed to remove legacy directory " << legacy_path);
-    }
-    if (symlink(instance_home_dir.c_str(), legacy_path.c_str())) {
-      return CF_ERRNO("symlink(\"" << instance_home_dir << "\", \""
-                                   << legacy_path << "\") failed");
-    }
-    std::string cuttlefish_path = group_creation_info.home + "/cuttlefish/";
-    legacy_path = system_wide_home + "/cuttlefish";
-    if (DirectoryExists(legacy_path,  true)) {
-      CF_EXPECT(RecursivelyRemoveDirectory(legacy_path),
-                "Failed to remove legacy directory " << legacy_path);
-    }
-    if (symlink(cuttlefish_path.c_str(), legacy_path.c_str())) {
-      return CF_ERRNO("symlink(\"" << cuttlefish_path << "\", \"" << legacy_path
-                                   << "\") failed");
-    }
-    std::string cuttlefish_assembly_path = cuttlefish_path + "assembly/";
-    legacy_path = system_wide_home + "/cuttlefish_assembly";
-    if (DirectoryExists(legacy_path,  true)) {
-      CF_EXPECT(RecursivelyRemoveDirectory(legacy_path),
-                "Failed to remove legacy directory " << legacy_path);
-    }
-    if (symlink(cuttlefish_assembly_path.c_str(), legacy_path.c_str())) {
-      return CF_ERRNO("symlink(\"" << cuttlefish_assembly_path << "\", \""
-                                   << legacy_path << "\") failed");
-    }
-    std::string config_path =
-        cuttlefish_assembly_path + "cuttlefish_config.json";
-    legacy_path = system_wide_home + "/.cuttlefish_config.json";
-    if (FileExists(legacy_path,  false)) {
-      CF_EXPECT(RemoveFile(legacy_path),
-                "Failed to remove instance_dir symlink " << legacy_path);
-    }
-    if (symlink(config_path.c_str(), legacy_path.c_str())) {
-      return CF_ERRNO("symlink(\"" << config_path << "\", \"" << legacy_path
-                                   << "\") failed");
-    }
+    // later on, we link cuttlefish_runtime to cuttlefish_runtime.smallest_id
+    smallest_id = std::min(smallest_id, instance.instance_id_);
+    const std::string instance_home_dir =
+        fmt::format("{}/cuttlefish/instances/cvd-{}", group_creation_info.home,
+                    instance.instance_id_);
+    CF_EXPECT(
+        EnsureSymlink(instance_home_dir,
+                      fmt::format("{}/cuttlefish_runtime.{}", system_wide_home,
+                                  instance.instance_id_)));
+    CF_EXPECT(EnsureSymlink(group_creation_info.home + "/cuttlefish",
+                            system_wide_home + "/cuttlefish"));
+    CF_EXPECT(EnsureSymlink(group_creation_info.home +
+                                "/cuttlefish/assembly/cuttlefish_config.json",
+                            system_wide_home + "/.cuttlefish_config.json"));
   }
+
+  // create cuttlefish_runtime to cuttlefish_runtime.id
+  CF_EXPECT_NE(std::numeric_limits<unsigned>::max(), smallest_id,
+               "The group did not have any instance, which is not expected.");
+  const std::string instance_runtime_dir =
+      fmt::format("{}/cuttlefish_runtime.{}", system_wide_home, smallest_id);
+  const std::string runtime_dir_link = system_wide_home + "/cuttlefish_runtime";
+  CF_EXPECT(EnsureSymlink(instance_runtime_dir, runtime_dir_link));
   return {};
 }
 
diff --git a/host/commands/cvd/server_command/status.cpp b/host/commands/cvd/server_command/status.cpp
index 9655264..7e0eda2 100644
--- a/host/commands/cvd/server_command/status.cpp
+++ b/host/commands/cvd/server_command/status.cpp
@@ -202,6 +202,10 @@
     return HandleHelp(request);
   }
 
+  const auto uid = CF_EXPECT(request.Credentials()).uid;
+  if (instance_manager_.AllGroupNames(uid).empty()) {
+    return CF_EXPECT(NoGroupResponse(request));
+  }
   RequestWithStdio new_request = CF_EXPECT(ProcessInstanceNameFlag(request));
 
   auto [entire_stderr_msg, instances_json, response] =
diff --git a/host/commands/cvd/server_command/utils.cpp b/host/commands/cvd/server_command/utils.cpp
index cf6566e..b1697b8 100644
--- a/host/commands/cvd/server_command/utils.cpp
+++ b/host/commands/cvd/server_command/utils.cpp
@@ -19,6 +19,7 @@
 #include <fmt/core.h>
 
 #include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/contains.h"
 #include "common/libs/utils/files.h"
 #include "common/libs/utils/flag_parser.h"
@@ -211,20 +212,77 @@
 static constexpr char kTerminalRed[] = "\033[0;31m";
 static constexpr char kTerminalReset[] = "\033[0m";
 
+std::string TerminalColor(const bool is_tty, TerminalColors color) {
+  if (!is_tty) {
+    return "";
+  }
+  switch (color) {
+    case TerminalColors::kReset: {
+      return kTerminalReset;
+    }
+    case TerminalColors::kBoldRed: {
+      return kTerminalBoldRed;
+    }
+    case TerminalColors::kCyan: {
+      return kTerminalCyan;
+    }
+    case TerminalColors::kRed: {
+      return kTerminalRed;
+    }
+    default:
+      return kTerminalReset;
+  }
+}
+
 Result<cvd::Response> NoGroupResponse(const RequestWithStdio& request) {
   cvd::Response response;
   response.mutable_command_response();
   response.mutable_status()->set_code(cvd::Status::OK);
   const uid_t uid = CF_EXPECT(request.Credentials()).uid;
+  const bool is_tty = request.Out()->IsOpen() && request.Out()->IsATTY();
   auto notice = fmt::format(
-      "Command `{}{}{}` is not applicable: {}{}{} (uid: '{}{}{}')",
-      kTerminalRed, fmt::join(request.Message().command_request().args(), " "),
-      kTerminalReset, kTerminalBoldRed, "no device", kTerminalReset,
-      kTerminalCyan, uid, kTerminalReset);
+      "Command `{}{}{}` is not applicable:\n  {}{}{} (uid: '{}{}{}')",
+      TerminalColor(is_tty, TerminalColors::kRed),
+      fmt::join(request.Message().command_request().args(), " "),
+      TerminalColor(is_tty, TerminalColors::kReset),
+      TerminalColor(is_tty, TerminalColors::kBoldRed), "no device",
+      TerminalColor(is_tty, TerminalColors::kReset),
+      TerminalColor(is_tty, TerminalColors::kCyan), uid,
+      TerminalColor(is_tty, TerminalColors::kReset));
   CF_EXPECT_EQ(WriteAll(request.Out(), notice + "\n"), notice.size() + 1);
 
   response.mutable_status()->set_message(notice);
   return response;
 }
 
+Result<cvd::Response> NoTTYResponse(const RequestWithStdio& request) {
+  cvd::Response response;
+  response.mutable_command_response();
+  response.mutable_status()->set_code(cvd::Status::OK);
+  const uid_t uid = CF_EXPECT(request.Credentials()).uid;
+  const bool is_tty = request.Out()->IsOpen() && request.Out()->IsATTY();
+  auto notice = fmt::format(
+      "Command `{}{}{}` is not applicable:\n  {}{}{} (uid: '{}{}{}')",
+      TerminalColor(is_tty, TerminalColors::kRed),
+      fmt::join(request.Message().command_request().args(), " "),
+      TerminalColor(is_tty, TerminalColors::kReset),
+      TerminalColor(is_tty, TerminalColors::kBoldRed),
+      "No terminal/tty for selecting one of multiple Cuttlefish groups",
+      TerminalColor(is_tty, TerminalColors::kReset),
+      TerminalColor(is_tty, TerminalColors::kCyan), uid,
+      TerminalColor(is_tty, TerminalColors::kReset));
+  CF_EXPECT_EQ(WriteAll(request.Out(), notice + "\n"), notice.size() + 1);
+  response.mutable_status()->set_message(notice);
+  return response;
+}
+
+Result<cvd::Response> WriteToFd(SharedFD fd, const std::string& output) {
+  cvd::Response response;
+  auto written_size = WriteAll(fd, output);
+  CF_EXPECT_EQ(output.size(), written_size, fd->StrError());
+  response.mutable_command_response();  // Sets oneof member
+  response.mutable_status()->set_code(cvd::Status::OK);
+  return response;
+}
+
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/server_command/utils.h b/host/commands/cvd/server_command/utils.h
index 9052d5c..8da9a43 100644
--- a/host/commands/cvd/server_command/utils.h
+++ b/host/commands/cvd/server_command/utils.h
@@ -25,6 +25,7 @@
 
 #include "cvd_server.pb.h"
 
+#include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/result.h"
 #include "common/libs/utils/subprocess.h"
 #include "host/commands/cvd/server_client.h"
@@ -81,4 +82,19 @@
 // The function does not verify that.
 Result<cvd::Response> NoGroupResponse(const RequestWithStdio& request);
 
+// Call this when there is more than one group, which the selector flags are
+// not sufficients to choose one from. The function does not verify that.
+Result<cvd::Response> NoTTYResponse(const RequestWithStdio& request);
+
+enum class TerminalColors : int {
+  kReset = 0,
+  kBoldRed = 1,
+  kCyan = 2,
+  kRed = 3,
+};
+
+std::string TerminalColor(const bool is_tty, TerminalColors color);
+
+Result<cvd::Response> WriteToFd(SharedFD fd, const std::string& output);
+
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/Android.bp b/host/commands/run_cvd/Android.bp
index a358b04..75f072b 100644
--- a/host/commands/run_cvd/Android.bp
+++ b/host/commands/run_cvd/Android.bp
@@ -40,6 +40,7 @@
         "launch/root_canal.cpp",
         "launch/casimir.cpp",
         "launch/pica.cpp",
+        "launch/screen_recording_server.cpp",
         "launch/secure_env.cpp",
         "launch/secure_env_files.cpp",
         "launch/webrtc_recorder.cpp",
diff --git a/host/commands/run_cvd/launch/launch.h b/host/commands/run_cvd/launch/launch.h
index a78dde9..8d17311 100644
--- a/host/commands/run_cvd/launch/launch.h
+++ b/host/commands/run_cvd/launch/launch.h
@@ -109,6 +109,8 @@
                                  const CuttlefishConfig::InstanceSpecific>>
 NetsimServerComponent();
 
+Result<std::optional<MonitorCommand>> ScreenRecordingServer(GrpcSocketCreator&);
+
 Result<MonitorCommand> SecureEnv(const CuttlefishConfig&,
                                  const CuttlefishConfig::InstanceSpecific&,
                                  AutoSecureEnvFiles::Type&,
diff --git a/host/commands/run_cvd/launch/screen_recording_server.cpp b/host/commands/run_cvd/launch/screen_recording_server.cpp
new file mode 100644
index 0000000..7848784
--- /dev/null
+++ b/host/commands/run_cvd/launch/screen_recording_server.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/run_cvd/launch/launch.h"
+
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <fruit/fruit.h>
+
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
+#include "host/commands/run_cvd/launch/grpc_socket_creator.h"
+#include "host/libs/config/command_source.h"
+#include "host/libs/config/known_paths.h"
+
+namespace cuttlefish {
+
+Result<std::optional<MonitorCommand>> ScreenRecordingServer(
+    GrpcSocketCreator& grpc_socket) {
+  Command screen_recording_server_cmd(ScreenRecordingServerBinary());
+  screen_recording_server_cmd.AddParameter(
+      "-grpc_uds_path=", grpc_socket.CreateGrpcSocket("ScreenRecordingServer"));
+  return screen_recording_server_cmd;
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/run_cvd/main.cc b/host/commands/run_cvd/main.cc
index 015b91f..d8db441 100644
--- a/host/commands/run_cvd/main.cc
+++ b/host/commands/run_cvd/main.cc
@@ -140,6 +140,7 @@
       .install(FastbootConfigFragmentComponent)
       .install(bootStateMachineComponent)
       .install(AutoCmd<CasimirControlServer>::Component)
+      .install(AutoCmd<ScreenRecordingServer>::Component)
       .install(ConfigFlagPlaceholder)
       .install(CustomActionsComponent)
       .install(LaunchAdbComponent)
@@ -253,7 +254,9 @@
     CF_EXPECT(late_injected->LateInject(injector));
   }
 
-  MetricsReceiver::LogMetricsVMStart();
+  if (config->enable_metrics() == cuttlefish::CuttlefishConfig::Answer::kYes) {
+    MetricsReceiver::LogMetricsVMStart();
+  }
 
   auto instance_bindings = injector.getMultibindings<InstanceLifecycle>();
   CF_EXPECT(instance_bindings.size() == 1);
diff --git a/host/commands/run_cvd/server_loop_impl.cpp b/host/commands/run_cvd/server_loop_impl.cpp
index 985f102..825cd88 100644
--- a/host/commands/run_cvd/server_loop_impl.cpp
+++ b/host/commands/run_cvd/server_loop_impl.cpp
@@ -227,10 +227,8 @@
         client->Write(&response, sizeof(response));
         break;
       }
-      auto powerwash = PowerwashFiles();
-      if (!powerwash.ok()) {
-        LOG(ERROR) << "Powerwashing files failed:\n"
-                   << powerwash.error().FormatForEnv();
+      if (!PowerwashFiles()) {
+        LOG(ERROR) << "Powerwashing files failed.";
         auto response = LauncherResponse::kError;
         client->Write(&response, sizeof(response));
         break;
@@ -309,7 +307,7 @@
   }
 }
 
-Result<void> ServerLoopImpl::PowerwashFiles() {
+bool ServerLoopImpl::PowerwashFiles() {
   DeleteFifos();
 
   // TODO(b/269669405): Figure out why this file is not being deleted
@@ -320,15 +318,15 @@
 
   auto kregistry_path = instance_.access_kregistry_path();
   unlink(kregistry_path.c_str());
-  CF_EXPECT(CreateBlankImage(kregistry_path, 2 /* mb */, "none"));
+  CreateBlankImage(kregistry_path, 2 /* mb */, "none");
 
   auto hwcomposer_pmem_path = instance_.hwcomposer_pmem_path();
   unlink(hwcomposer_pmem_path.c_str());
-  CF_EXPECT(CreateBlankImage(hwcomposer_pmem_path, 2 /* mb */, "none"));
+  CreateBlankImage(hwcomposer_pmem_path, 2 /* mb */, "none");
 
   auto pstore_path = instance_.pstore_path();
   unlink(pstore_path.c_str());
-  CF_EXPECT(CreateBlankImage(pstore_path, 2 /* mb */, "none"));
+  CreateBlankImage(pstore_path, 2 /* mb */, "none");
 
   auto sdcard_path = instance_.sdcard_path();
   auto sdcard_size = FileSize(sdcard_path);
@@ -336,7 +334,7 @@
   // round up
   auto sdcard_mb_size = (sdcard_size + (1 << 20) - 1) / (1 << 20);
   LOG(DEBUG) << "Size in mb is " << sdcard_mb_size;
-  CF_EXPECT(CreateBlankImage(sdcard_path, sdcard_mb_size, "sdcard"));
+  CreateBlankImage(sdcard_path, sdcard_mb_size, "sdcard");
 
   struct OverlayFile {
     std::string name;
@@ -358,10 +356,13 @@
     auto composite_disk_path = overlay_file.composite_disk_path.c_str();
 
     unlink(overlay_path.c_str());
-    CF_EXPECT(CreateQcowOverlay(instance_.crosvm_binary(), composite_disk_path,
-                                overlay_path));
+    if (!CreateQcowOverlay(instance_.crosvm_binary(), composite_disk_path,
+                           overlay_path)) {
+      LOG(ERROR) << "CreateQcowOverlay failed";
+      return false;
+    }
   }
-  return {};
+  return true;
 }
 
 void ServerLoopImpl::RestartRunCvd(int notification_fd) {
diff --git a/host/commands/run_cvd/server_loop_impl.h b/host/commands/run_cvd/server_loop_impl.h
index 8166d4f..81494e2 100644
--- a/host/commands/run_cvd/server_loop_impl.h
+++ b/host/commands/run_cvd/server_loop_impl.h
@@ -84,7 +84,7 @@
                               ProcessMonitor& process_monitor);
 
   void DeleteFifos();
-  Result<void> PowerwashFiles();
+  bool PowerwashFiles();
   void RestartRunCvd(int notification_fd);
   static bool CreateQcowOverlay(const std::string& crosvm_path,
                                 const std::string& backing_file,
diff --git a/host/commands/screen_recording_server/Android.bp b/host/commands/screen_recording_server/Android.bp
new file mode 100644
index 0000000..e228310
--- /dev/null
+++ b/host/commands/screen_recording_server/Android.bp
@@ -0,0 +1,116 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+    name: "libscreen_recording_server",
+    shared_libs: [
+        "libprotobuf-cpp-full",
+        "libgrpc++_unsecure",
+    ],
+    static_libs: [
+        "libgflags",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+    generated_headers: [
+        "ScreenRecordingServerProto_h",
+    ],
+    generated_sources: [
+        "ScreenRecordingServerProto_cc",
+    ],
+    export_generated_headers: [
+        "ScreenRecordingServerProto_h",
+    ],
+    defaults: ["cuttlefish_host"],
+    include_dirs: [
+        "external/grpc-grpc/include",
+        "external/protobuf/src",
+    ],
+    target: {
+        darwin: {
+            enabled: true,
+        },
+    },
+}
+
+cc_binary_host {
+    name: "screen_recording_server",
+    shared_libs: [
+        "libprotobuf-cpp-full",
+        "libgrpc++_unsecure",
+    ],
+    static_libs: [
+        "libcuttlefish_host_config",
+        "libgflags",
+        "libscreen_recording_server",
+        "libgrpc++_reflection",
+    ],
+    srcs: [
+        "main.cpp",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+    defaults: ["cuttlefish_host"],
+    target: {
+        darwin: {
+            enabled: true,
+        },
+    },
+}
+
+filegroup {
+    name: "ScreenRecordingServerProto",
+    srcs: [
+        "screen_recording.proto",
+        ":libprotobuf-internal-protos",
+    ],
+}
+
+genrule {
+    name: "ScreenRecordingServerProto_h",
+    tools: [
+        "aprotoc",
+        "protoc-gen-grpc-cpp-plugin",
+    ],
+    cmd: "$(location aprotoc) -Idevice/google/cuttlefish/host/commands/screen_recording_server -Iexternal/protobuf/src --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-cpp-plugin) $(in) --grpc_out=$(genDir) --cpp_out=$(genDir)",
+    srcs: [
+        ":ScreenRecordingServerProto",
+    ],
+    out: [
+        "screen_recording.grpc.pb.h",
+        "screen_recording.pb.h",
+    ],
+}
+
+genrule {
+    name: "ScreenRecordingServerProto_cc",
+    tools: [
+        "aprotoc",
+        "protoc-gen-grpc-cpp-plugin",
+    ],
+    cmd: "$(location aprotoc) -Idevice/google/cuttlefish/host/commands/screen_recording_server -Iexternal/protobuf/src --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-cpp-plugin) $(in) --grpc_out=$(genDir) --cpp_out=$(genDir)",
+    srcs: [
+        ":ScreenRecordingServerProto",
+    ],
+    out: [
+        "screen_recording.grpc.pb.cc",
+        "screen_recording.pb.cc",
+    ],
+}
diff --git a/host/commands/screen_recording_server/main.cpp b/host/commands/screen_recording_server/main.cpp
new file mode 100644
index 0000000..308ec49
--- /dev/null
+++ b/host/commands/screen_recording_server/main.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include <gflags/gflags.h>
+#include <grpcpp/ext/proto_server_reflection_plugin.h>
+#include <grpcpp/grpcpp.h>
+#include <grpcpp/health_check_service_interface.h>
+
+#include "screen_recording.grpc.pb.h"
+
+using google::protobuf::Empty;
+using grpc::Server;
+using grpc::ServerBuilder;
+using grpc::ServerContext;
+using grpc::Status;
+using screenrecordingserver::ExampleReply;
+using screenrecordingserver::ScreenRecordingService;
+
+DEFINE_string(grpc_uds_path, "", "grpc_uds_path");
+
+class ScreenRecordingServiceImpl final
+    : public ScreenRecordingService::Service {
+  // TODO(b/315845821): Remove this example method, and fill with real contents.
+  Status ExampleMethod(ServerContext* context, const Empty* request,
+                       ExampleReply* reply) override {
+    reply->set_message("This is a example method");
+    return Status::OK;
+  }
+};
+
+void RunServer() {
+  std::string server_address("unix:" + FLAGS_grpc_uds_path);
+  ScreenRecordingServiceImpl service;
+
+  grpc::EnableDefaultHealthCheckService(true);
+  grpc::reflection::InitProtoReflectionServerBuilderPlugin();
+  ServerBuilder builder;
+  // Listen on the given address without any authentication mechanism.
+  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
+  // Register "service" as the instance through which we'll communicate with
+  // clients. In this case it corresponds to an *synchronous* service.
+  builder.RegisterService(&service);
+  // Finally assemble the server.
+  std::unique_ptr<Server> server(builder.BuildAndStart());
+  std::cout << "Server listening on " << server_address << std::endl;
+
+  // Wait for the server to shutdown. Note that some other thread must be
+  // responsible for shutting down the server for this call to ever return.
+  server->Wait();
+}
+
+int main(int argc, char** argv) {
+  ::gflags::ParseCommandLineFlags(&argc, &argv, true);
+  RunServer();
+
+  return 0;
+}
diff --git a/host/commands/screen_recording_server/screen_recording.proto b/host/commands/screen_recording_server/screen_recording.proto
new file mode 100644
index 0000000..62d5b4f
--- /dev/null
+++ b/host/commands/screen_recording_server/screen_recording.proto
@@ -0,0 +1,28 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package screenrecordingserver;
+
+import "google/protobuf/empty.proto";
+
+service ScreenRecordingService {
+  // TODO(b/315845821): Remove this example method, and fill with real contents.
+  rpc ExampleMethod (google.protobuf.Empty) returns (ExampleReply) {}
+}
+
+message ExampleReply {
+  string message = 1;
+}
diff --git a/host/commands/secure_env/encrypted_serializable.cpp b/host/commands/secure_env/encrypted_serializable.cpp
index 75891fc..6c1c200 100644
--- a/host/commands/secure_env/encrypted_serializable.cpp
+++ b/host/commands/secure_env/encrypted_serializable.cpp
@@ -42,7 +42,7 @@
     TPM2B_PRIVATE* key_private_out, // out
     TpmObjectSlot* key_slot_out) { // out
   TPM2B_AUTH authValue = {};
-  auto rc = Esys_TR_SetAuth(resource_manager.Esys(), parent_key, &authValue);
+  auto rc = Esys_TR_SetAuth(*resource_manager.Esys(), parent_key, &authValue);
   if (rc != TSS2_RC_SUCCESS) {
     LOG(ERROR) << "Esys_TR_SetAuth failed with return code " << rc
                << " (" << Tss2_RC_Decode(rc) << ")";
@@ -90,16 +90,16 @@
   TPM2B_PRIVATE* key_private = nullptr;
   // TODO(schuffelen): Use Esys_Create when key_slot is NULL
   rc = Esys_CreateLoaded(
-    /* esysContext */ resource_manager.Esys(),
-    /* primaryHandle */ parent_key,
-    /* shandle1 */ ESYS_TR_PASSWORD,
-    /* shandle2 */ ESYS_TR_NONE,
-    /* shandle3 */ ESYS_TR_NONE,
-    /* inSensitive */ &in_sensitive,
-    /* inPublic */ &public_template,
-    /* objectHandle */ &raw_handle,
-    /* outPrivate */ &key_private,
-    /* outPublic */ &key_public);
+      /* esysContext */ *resource_manager.Esys(),
+      /* primaryHandle */ parent_key,
+      /* shandle1 */ ESYS_TR_PASSWORD,
+      /* shandle2 */ ESYS_TR_NONE,
+      /* shandle3 */ ESYS_TR_NONE,
+      /* inSensitive */ &in_sensitive,
+      /* inPublic */ &public_template,
+      /* objectHandle */ &raw_handle,
+      /* outPrivate */ &key_private,
+      /* outPublic */ &key_public);
   if (rc != TSS2_RC_SUCCESS) {
     LOG(ERROR) << "Esys_CreateLoaded failed with return code " << rc
                << " (" << Tss2_RC_Decode(rc) << ")";
@@ -113,7 +113,7 @@
   Esys_Free(key_public);
   Esys_Free(key_private);
   if (key_slot_out) {
-    rc = Esys_TR_SetAuth(resource_manager.Esys(), raw_handle, &authValue);
+    rc = Esys_TR_SetAuth(*resource_manager.Esys(), raw_handle, &authValue);
     if (rc != TSS2_RC_SUCCESS) {
       LOG(ERROR) << "Esys_TR_SetAuth failed with return code " << rc
                 << " (" << Tss2_RC_Decode(rc) << ")";
@@ -138,15 +138,9 @@
     LOG(ERROR) << "No slots available";
     return {};
   }
-  auto rc = Esys_Load(
-      resource_manager.Esys(),
-      parent_key,
-      ESYS_TR_PASSWORD,
-      ESYS_TR_NONE,
-      ESYS_TR_NONE,
-      key_private,
-      key_public,
-      &raw_handle);
+  auto rc = Esys_Load(*resource_manager.Esys(), parent_key, ESYS_TR_PASSWORD,
+                      ESYS_TR_NONE, ESYS_TR_NONE, key_private, key_public,
+                      &raw_handle);
   if (rc != TSS2_RC_SUCCESS) {
     LOG(ERROR) << "Esys_Load failed with return code " << rc
                << " (" << Tss2_RC_Decode(rc) << ")";
@@ -203,7 +197,7 @@
 
   TPM2B_IV iv;
   iv.size = sizeof(iv.buffer);
-  auto rc = TpmRandomSource(resource_manager_.Esys())
+  auto rc = TpmRandomSource(resource_manager_)
                 .GenerateRandom(iv.buffer, sizeof(iv.buffer));
   if (rc != KM_ERROR_OK) {
     LOG(ERROR) << "Failed to get random data";
@@ -222,7 +216,7 @@
   }
   std::vector<uint8_t> encrypted(encrypted_size, 0);
   if (!TpmEncrypt(  //
-          resource_manager_.Esys(), key_slot->get(), TpmAuth(ESYS_TR_PASSWORD),
+          *resource_manager_.Esys(), key_slot->get(), TpmAuth(ESYS_TR_PASSWORD),
           iv, unencrypted.data(), encrypted.data(), encrypted_size)) {
     LOG(ERROR) << "Encryption failed";
     return buf;
@@ -305,7 +299,7 @@
   }
   std::vector<uint8_t> decrypted_data(encrypted_size, 0);
   if (!TpmDecrypt(  //
-          resource_manager_.Esys(), key_slot->get(), TpmAuth(ESYS_TR_PASSWORD),
+          *resource_manager_.Esys(), key_slot->get(), TpmAuth(ESYS_TR_PASSWORD),
           iv, encrypted_data.data(), decrypted_data.data(), encrypted_size)) {
     LOG(ERROR) << "Failed to decrypt encrypted data";
     return false;
diff --git a/host/commands/secure_env/primary_key_builder.cpp b/host/commands/secure_env/primary_key_builder.cpp
index 173f31e..ff0d2d7 100644
--- a/host/commands/secure_env/primary_key_builder.cpp
+++ b/host/commands/secure_env/primary_key_builder.cpp
@@ -67,7 +67,7 @@
     TpmResourceManager& resource_manager) {
   TPM2B_AUTH authValue = {};
   auto rc =
-      Esys_TR_SetAuth(resource_manager.Esys(), ESYS_TR_RH_OWNER, &authValue);
+      Esys_TR_SetAuth(*resource_manager.Esys(), ESYS_TR_RH_OWNER, &authValue);
   if (rc != TSS2_RC_SUCCESS) {
     LOG(ERROR) << "Esys_TR_SetAuth failed with return code " << rc
                << " (" << Tss2_RC_Decode(rc) << ")";
@@ -97,16 +97,16 @@
   // Since this is a primary key, it's generated deterministically. It would
   // also be possible to generate this once and hold it in storage.
   rc = Esys_CreateLoaded(
-    /* esysContext */ resource_manager.Esys(),
-    /* primaryHandle */ ESYS_TR_RH_OWNER,
-    /* shandle1 */ ESYS_TR_PASSWORD,
-    /* shandle2 */ ESYS_TR_NONE,
-    /* shandle3 */ ESYS_TR_NONE,
-    /* inSensitive */ &in_sensitive,
-    /* inPublic */ &public_template,
-    /* objectHandle */ &raw_handle,
-    /* outPrivate */ nullptr,
-    /* outPublic */ nullptr);
+      /* esysContext */ *resource_manager.Esys(),
+      /* primaryHandle */ ESYS_TR_RH_OWNER,
+      /* shandle1 */ ESYS_TR_PASSWORD,
+      /* shandle2 */ ESYS_TR_NONE,
+      /* shandle3 */ ESYS_TR_NONE,
+      /* inSensitive */ &in_sensitive,
+      /* inPublic */ &public_template,
+      /* objectHandle */ &raw_handle,
+      /* outPrivate */ nullptr,
+      /* outPublic */ nullptr);
   if (rc != TSS2_RC_SUCCESS) {
     LOG(ERROR) << "Esys_CreateLoaded failed with return code " << rc
                << " (" << Tss2_RC_Decode(rc) << ")";
diff --git a/host/commands/secure_env/rust/Android.bp b/host/commands/secure_env/rust/Android.bp
index c5d6b01..6b04e7b 100644
--- a/host/commands/secure_env/rust/Android.bp
+++ b/host/commands/secure_env/rust/Android.bp
@@ -39,7 +39,7 @@
         "liblibc",
         "liblog_rust",
         "libnix",
-        "libprotobuf_deprecated",
+        "libprotobuf",
         "libsecure_env_tpm",
     ],
     defaults: ["cuttlefish_buildhost_only"],
@@ -73,7 +73,7 @@
         "liblibc",
         "liblog_rust",
         "libnix",
-        "libprotobuf_deprecated",
+        "libprotobuf",
         "libsecure_env_tpm",
     ],
     defaults: ["cuttlefish_buildhost_only"],
diff --git a/host/commands/secure_env/storage/tpm_storage.cpp b/host/commands/secure_env/storage/tpm_storage.cpp
index f1d451f..4bdb54e 100644
--- a/host/commands/secure_env/storage/tpm_storage.cpp
+++ b/host/commands/secure_env/storage/tpm_storage.cpp
@@ -58,26 +58,26 @@
   auto handle_optional = CF_EXPECT(GetHandle(key));
   auto handle = CF_EXPECT(handle_optional.value());
   auto close_tr = [this](ESYS_TR* handle) {
-    Esys_TR_Close(resource_manager_.Esys(), handle);
+    Esys_TR_Close(*resource_manager_.Esys(), handle);
     delete handle;
   };
   std::unique_ptr<ESYS_TR, decltype(close_tr)> nv_handle(new ESYS_TR, close_tr);
   auto rc = Esys_TR_FromTPMPublic(
-    /* esysContext */ resource_manager_.Esys(),
-    /* tpm_handle */ handle,
-    /* optionalSession1 */ ESYS_TR_NONE,
-    /* optionalSession2 */ ESYS_TR_NONE,
-    /* optionalSession3 */ ESYS_TR_NONE,
-    /* object */ nv_handle.get());
+      /* esysContext */ *resource_manager_.Esys(),
+      /* tpm_handle */ handle,
+      /* optionalSession1 */ ESYS_TR_NONE,
+      /* optionalSession2 */ ESYS_TR_NONE,
+      /* optionalSession3 */ ESYS_TR_NONE,
+      /* object */ nv_handle.get());
   CF_EXPECTF(rc == TPM2_RC_SUCCESS, "Esys_TR_FromTPMPublic failed: {}: {}",
              rc, Tss2_RC_Decode(rc));
 
   TPM2B_AUTH auth = { .size = 0, .buffer = {} };
-  Esys_TR_SetAuth(resource_manager_.Esys(), *nv_handle, &auth);
+  Esys_TR_SetAuth(*resource_manager_.Esys(), *nv_handle, &auth);
 
   TPM2B_NV_PUBLIC* public_area;
   rc = Esys_NV_ReadPublic(
-      /* esysContext */ resource_manager_.Esys(),
+      /* esysContext */ *resource_manager_.Esys(),
       /* nvIndex */ *nv_handle,
       /* shandle1 */ ESYS_TR_NONE,
       /* shandle2 */ ESYS_TR_NONE,
@@ -90,7 +90,7 @@
   std::unique_ptr<TPM2B_NV_PUBLIC, decltype(Esys_Free)*> public_deleter(public_area, Esys_Free);
   TPM2B_MAX_NV_BUFFER* buffer = nullptr;
   rc = Esys_NV_Read(
-      /* esysContext */ resource_manager_.Esys(),
+      /* esysContext */ *resource_manager_.Esys(),
       /* authHandle */ *nv_handle,
       /* nvIndex */ *nv_handle,
       /* shandle1 */ ESYS_TR_PASSWORD,
@@ -116,7 +116,7 @@
   auto handle = CF_EXPECT(handle_optional.value());
   ESYS_TR nv_handle;
   auto rc = Esys_TR_FromTPMPublic(
-      /* esysContext */ resource_manager_.Esys(),
+      /* esysContext */ *resource_manager_.Esys(),
       /* tpm_handle */ handle,
       /* optionalSession1 */ ESYS_TR_NONE,
       /* optionalSession2 */ ESYS_TR_NONE,
@@ -126,14 +126,14 @@
              rc, Tss2_RC_Decode(rc));
 
   TPM2B_AUTH auth = { .size = 0, .buffer = {} };
-  Esys_TR_SetAuth(resource_manager_.Esys(), nv_handle, &auth);
+  Esys_TR_SetAuth(*resource_manager_.Esys(), nv_handle, &auth);
 
   TPM2B_MAX_NV_BUFFER buffer;
   buffer.size = data.size;
   std::memcpy(buffer.buffer, data.payload, data.size);
 
   rc = Esys_NV_Write(
-      /* esysContext */ resource_manager_.Esys(),
+      /* esysContext */ *resource_manager_.Esys(),
       /* authHandle */ nv_handle,
       /* nvIndex */ nv_handle,
       /* shandle1 */ ESYS_TR_PASSWORD,
@@ -141,7 +141,7 @@
       /* shandle3 */ ESYS_TR_NONE,
       /* data */ &buffer,
       /* offset */ 0);
-  Esys_TR_Close(resource_manager_.Esys(), &nv_handle);
+  Esys_TR_Close(*resource_manager_.Esys(), &nv_handle);
   CF_EXPECTF(rc == TSS2_RC_SUCCESS, "Esys_NV_Write failed with return code {} ({})",
              rc, Tss2_RC_Decode(rc));
 
@@ -149,7 +149,7 @@
 }
 
 TPM2_HANDLE TpmStorage::GenerateRandomHandle() {
-  TpmRandomSource random_source{resource_manager_.Esys()};
+  TpmRandomSource random_source{resource_manager_};
   TPM2_HANDLE handle = 0;
   random_source.GenerateRandom(reinterpret_cast<uint8_t*>(&handle), sizeof(handle));
   if (handle == 0) {
@@ -191,22 +191,22 @@
       }
     };
     TPM2B_AUTH auth = { .size = 0, .buffer = {} };
-    Esys_TR_SetAuth(resource_manager_.Esys(), ESYS_TR_RH_OWNER, &auth);
+    Esys_TR_SetAuth(*resource_manager_.Esys(), ESYS_TR_RH_OWNER, &auth);
     ESYS_TR nv_handle;
     auto rc = Esys_NV_DefineSpace(
-      /* esysContext */ resource_manager_.Esys(),
-      /* authHandle */ ESYS_TR_RH_OWNER,
-      /* shandle1 */ ESYS_TR_PASSWORD,
-      /* shandle2 */ ESYS_TR_NONE,
-      /* shandle3 */ ESYS_TR_NONE,
-      /* auth */ &auth,
-      /* publicInfo */ &public_info,
-      /* nvHandle */ &nv_handle);
+        /* esysContext */ *resource_manager_.Esys(),
+        /* authHandle */ ESYS_TR_RH_OWNER,
+        /* shandle1 */ ESYS_TR_PASSWORD,
+        /* shandle2 */ ESYS_TR_NONE,
+        /* shandle3 */ ESYS_TR_NONE,
+        /* auth */ &auth,
+        /* publicInfo */ &public_info,
+        /* nvHandle */ &nv_handle);
     if (rc == TPM2_RC_NV_DEFINED) {
       LOG(VERBOSE) << "Esys_NV_DefineSpace failed with TPM2_RC_NV_DEFINED";
       continue;
     } else if (rc == TPM2_RC_SUCCESS) {
-      Esys_TR_Close(resource_manager_.Esys(), &nv_handle);
+      Esys_TR_Close(*resource_manager_.Esys(), &nv_handle);
       break;
     } else {
       LOG(DEBUG) << "Esys_NV_DefineSpace failed with " << rc << ": "
diff --git a/host/commands/secure_env/suspend_resume_handler.cpp b/host/commands/secure_env/suspend_resume_handler.cpp
index af14c8a..f1f126c 100644
--- a/host/commands/secure_env/suspend_resume_handler.cpp
+++ b/host/commands/secure_env/suspend_resume_handler.cpp
@@ -82,7 +82,8 @@
   const auto action_type = launcher_action.type;
   CF_EXPECTF(action_type == ExtendedActionType::kSuspend ||
                  action_type == ExtendedActionType::kResume,
-             "Unsupported ExtendedActionType \"{}\"", action_type);
+             "Unsupported ExtendedActionType \"{}\"",
+             fmt::underlying(action_type));
   return action_type;
 }
 
diff --git a/host/commands/secure_env/tpm_gatekeeper.cpp b/host/commands/secure_env/tpm_gatekeeper.cpp
index 3a3c44e..7cd9ec0 100644
--- a/host/commands/secure_env/tpm_gatekeeper.cpp
+++ b/host/commands/secure_env/tpm_gatekeeper.cpp
@@ -85,7 +85,7 @@
 
 void TpmGatekeeper::GetRandom(void* random, uint32_t requested_size) const {
   auto random_uint8 = reinterpret_cast<uint8_t*>(random);
-  TpmRandomSource(resource_manager_.Esys())
+  TpmRandomSource(resource_manager_)
       .GenerateRandom(random_uint8, requested_size);
 }
 
diff --git a/host/commands/secure_env/tpm_hmac.cpp b/host/commands/secure_env/tpm_hmac.cpp
index cbb6a53..a0b6a85 100644
--- a/host/commands/secure_env/tpm_hmac.cpp
+++ b/host/commands/secure_env/tpm_hmac.cpp
@@ -16,6 +16,7 @@
 #include "tpm_hmac.h"
 
 #include <android-base/logging.h>
+#include <tss2/tss2_esys.h>
 #include <tss2/tss2_rc.h>
 
 #include "host/commands/secure_env/primary_key_builder.h"
@@ -40,15 +41,9 @@
   buffer.size = data_size;
   memcpy(buffer.buffer, data, data_size);
   TPM2B_DIGEST* out_hmac = nullptr;
-  auto rc = Esys_HMAC(
-      resource_manager.Esys(),
-      key_handle,
-      auth.auth1(),
-      auth.auth2(),
-      auth.auth3(),
-      &buffer,
-      TPM2_ALG_NULL,
-      &out_hmac);
+  auto rc =
+      Esys_HMAC(*resource_manager.Esys(), key_handle, auth.auth1(),
+                auth.auth2(), auth.auth3(), &buffer, TPM2_ALG_NULL, &out_hmac);
   if (rc != TPM2_RC_SUCCESS) {
     LOG(ERROR) << "TPM2_HMAC failed: " << Tss2_RC_Decode(rc) << "(" << rc << ")";
     return {};
@@ -77,23 +72,18 @@
     LOG(ERROR) << "No slots available";
     return {};
   }
-  auto rc = Esys_HMAC_Start(
-      resource_manager.Esys(),
-      key_handle,
-      key_auth.auth1(),
-      key_auth.auth2(),
-      key_auth.auth3(),
-      &sequence_auth,
-      TPM2_ALG_NULL,
-      &sequence_handle);
+  auto locked_esys = resource_manager.Esys();
+  auto rc = Esys_HMAC_Start(*locked_esys, key_handle, key_auth.auth1(),
+                            key_auth.auth2(), key_auth.auth3(), &sequence_auth,
+                            TPM2_ALG_NULL, &sequence_handle);
   if (rc != TPM2_RC_SUCCESS) {
     LOG(ERROR) << "TPM2_HMAC_Start failed: " << Tss2_RC_Decode(rc)
                << "(" << rc << ")";
     return {};
   }
   slot->set(sequence_handle);
-  rc = Esys_TR_SetAuth(
-      resource_manager.Esys(), sequence_handle, &sequence_auth);
+  rc = Esys_TR_SetAuth(*locked_esys, sequence_handle,
+                       &sequence_auth);
   if (rc != TPM2_RC_SUCCESS) {
     LOG(ERROR) << "Esys_TR_SetAuth failed: " << Tss2_RC_Decode(rc)
                << "(" << rc << ")";
@@ -105,13 +95,8 @@
     buffer.size = TPM2_MAX_DIGEST_BUFFER;
     memcpy(buffer.buffer, &data[hashed], TPM2_MAX_DIGEST_BUFFER);
     hashed += TPM2_MAX_DIGEST_BUFFER;
-    rc = Esys_SequenceUpdate(
-        resource_manager.Esys(),
-        sequence_handle,
-        ESYS_TR_PASSWORD,
-        ESYS_TR_NONE,
-        ESYS_TR_NONE,
-        &buffer);
+    rc = Esys_SequenceUpdate(*locked_esys, sequence_handle, ESYS_TR_PASSWORD,
+                             ESYS_TR_NONE, ESYS_TR_NONE, &buffer);
     if (rc != TPM2_RC_SUCCESS) {
       LOG(ERROR) << "Esys_SequenceUpdate failed: " << Tss2_RC_Decode(rc)
                 << "(" << rc << ")";
@@ -122,16 +107,9 @@
   memcpy(buffer.buffer, &data[hashed], buffer.size);
   TPM2B_DIGEST* out_hmac = nullptr;
   TPMT_TK_HASHCHECK* validation = nullptr;
-  rc = Esys_SequenceComplete(
-      resource_manager.Esys(),
-      sequence_handle,
-      ESYS_TR_PASSWORD,
-      ESYS_TR_NONE,
-      ESYS_TR_NONE,
-      &buffer,
-      TPM2_RH_OWNER,
-      &out_hmac,
-      &validation);
+  rc = Esys_SequenceComplete(*locked_esys, sequence_handle, ESYS_TR_PASSWORD,
+                             ESYS_TR_NONE, ESYS_TR_NONE, &buffer, TPM2_RH_OWNER,
+                             &out_hmac, &validation);
   if (rc != TPM2_RC_SUCCESS) {
     LOG(ERROR) << "Esys_SequenceComplete failed: " << Tss2_RC_Decode(rc)
                << "(" << rc << ")";
@@ -155,7 +133,6 @@
     size_t data_size) {
   auto fn = data_size > TPM2_MAX_DIGEST_BUFFER ? SegmentedHmac : OneshotHmac;
 
-  auto with_tpm = resource_manager.Guard();
   return fn(resource_manager, key_handle, auth, data, data_size);
 }
 
diff --git a/host/commands/secure_env/tpm_keymaster_context.cpp b/host/commands/secure_env/tpm_keymaster_context.cpp
index bca81b8..dbd9e87 100644
--- a/host/commands/secure_env/tpm_keymaster_context.cpp
+++ b/host/commands/secure_env/tpm_keymaster_context.cpp
@@ -80,7 +80,7 @@
     : resource_manager_(resource_manager),
       enforcement_(enforcement),
       key_blob_maker_(new TpmKeyBlobMaker(resource_manager_)),
-      random_source_(new TpmRandomSource(resource_manager_.Esys())),
+      random_source_(new TpmRandomSource(resource_manager_)),
       attestation_context_(new TpmAttestationRecordContext),
       remote_provisioning_context_(
           new TpmRemoteProvisioningContext(resource_manager_)) {
diff --git a/host/commands/secure_env/tpm_keymaster_enforcement.cpp b/host/commands/secure_env/tpm_keymaster_enforcement.cpp
index 4a4af14..ef37453 100644
--- a/host/commands/secure_env/tpm_keymaster_enforcement.cpp
+++ b/host/commands/secure_env/tpm_keymaster_enforcement.cpp
@@ -172,7 +172,7 @@
     HmacSharingParameters* params) {
   if (!have_saved_params_) {
     saved_params_.seed = {};
-    TpmRandomSource random_source{resource_manager_.Esys()};
+    TpmRandomSource random_source{resource_manager_};
     auto rc = random_source.GenerateRandom(saved_params_.nonce,
                                            sizeof(saved_params_.nonce));
     if (rc != KM_ERROR_OK) {
diff --git a/host/commands/secure_env/tpm_random_source.cpp b/host/commands/secure_env/tpm_random_source.cpp
index 569edbc..9b8e3ae 100644
--- a/host/commands/secure_env/tpm_random_source.cpp
+++ b/host/commands/secure_env/tpm_random_source.cpp
@@ -13,16 +13,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "tpm_random_source.h"
+#include "host/commands/secure_env/tpm_random_source.h"
 
 #include <android-base/logging.h>
+#include "tpm_resource_manager.h"
 #include "tss2/tss2_esys.h"
 #include "tss2/tss2_rc.h"
 
 namespace cuttlefish {
 
-TpmRandomSource::TpmRandomSource(ESYS_CONTEXT* esys) : esys_(esys) {
-}
+TpmRandomSource::TpmRandomSource(TpmResourceManager& resource_manager)
+    : resource_manager_(resource_manager) {}
 
 keymaster_error_t TpmRandomSource::GenerateRandom(
     uint8_t* random, size_t requested_length) const {
@@ -32,9 +33,9 @@
   // TODO(b/158790549): Pipeline these calls.
   TPM2B_DIGEST* generated = nullptr;
   while (requested_length > sizeof(generated->buffer)) {
-    auto rc = Esys_GetRandom(esys_, ESYS_TR_NONE, ESYS_TR_NONE,
-                             ESYS_TR_NONE, sizeof(generated->buffer),
-                             &generated);
+    auto rc =
+        Esys_GetRandom(*resource_manager_.Esys(), ESYS_TR_NONE, ESYS_TR_NONE,
+                       ESYS_TR_NONE, sizeof(generated->buffer), &generated);
     if (rc != TSS2_RC_SUCCESS) {
       LOG(ERROR) << "Esys_GetRandom failed with " << rc << " ("
                  << Tss2_RC_Decode(rc) << ")";
@@ -46,8 +47,9 @@
     requested_length -= sizeof(generated->buffer);
     Esys_Free(generated);
   }
-  auto rc = Esys_GetRandom(esys_, ESYS_TR_NONE, ESYS_TR_NONE,
-                           ESYS_TR_NONE, requested_length, &generated);
+  auto rc =
+      Esys_GetRandom(*resource_manager_.Esys(), ESYS_TR_NONE, ESYS_TR_NONE,
+                     ESYS_TR_NONE, requested_length, &generated);
   if (rc != TSS2_RC_SUCCESS) {
     LOG(ERROR) << "Esys_GetRandom failed with " << rc << " ("
                 << Tss2_RC_Decode(rc) << ")";
@@ -75,12 +77,8 @@
     in_data.size = MAX_STIR_RANDOM_BUFFER_SIZE;
     buffer += MAX_STIR_RANDOM_BUFFER_SIZE;
     size -= MAX_STIR_RANDOM_BUFFER_SIZE;
-    auto rc = Esys_StirRandom(
-        esys_,
-        ESYS_TR_NONE,
-        ESYS_TR_NONE,
-        ESYS_TR_NONE,
-        &in_data);
+    auto rc = Esys_StirRandom(*resource_manager_.Esys(), ESYS_TR_NONE,
+                              ESYS_TR_NONE, ESYS_TR_NONE, &in_data);
     if (rc != TSS2_RC_SUCCESS) {
       LOG(ERROR) << "Esys_StirRandom failed with " << rc << "("
                  << Tss2_RC_Decode(rc) << ")";
@@ -91,12 +89,8 @@
     return KM_ERROR_OK;
   }
   memcpy(in_data.buffer, buffer, size);
-  auto rc = Esys_StirRandom(
-      esys_,
-      ESYS_TR_NONE,
-      ESYS_TR_NONE,
-      ESYS_TR_NONE,
-      &in_data);
+  auto rc = Esys_StirRandom(*resource_manager_.Esys(), ESYS_TR_NONE,
+                            ESYS_TR_NONE, ESYS_TR_NONE, &in_data);
   if (rc != TSS2_RC_SUCCESS) {
     LOG(ERROR) << "Esys_StirRandom failed with " << rc << "("
                 << Tss2_RC_Decode(rc) << ")";
diff --git a/host/commands/secure_env/tpm_random_source.h b/host/commands/secure_env/tpm_random_source.h
index c9a91c7..12d4de7 100644
--- a/host/commands/secure_env/tpm_random_source.h
+++ b/host/commands/secure_env/tpm_random_source.h
@@ -17,7 +17,7 @@
 
 #include <keymaster/random_source.h>
 
-struct ESYS_CONTEXT;
+#include "host/commands/secure_env/tpm_resource_manager.h"
 
 namespace cuttlefish {
 
@@ -28,15 +28,16 @@
  */
 class TpmRandomSource : public keymaster::RandomSource {
 public:
-  TpmRandomSource(ESYS_CONTEXT* esys);
-  virtual ~TpmRandomSource() = default;
+ TpmRandomSource(TpmResourceManager& resource_manager);
+ virtual ~TpmRandomSource() = default;
 
-  keymaster_error_t GenerateRandom(
-      uint8_t* buffer, size_t length) const override;
+ keymaster_error_t GenerateRandom(uint8_t* buffer,
+                                  size_t length) const override;
 
-  keymaster_error_t AddRngEntropy(const uint8_t*, size_t) const;
+ keymaster_error_t AddRngEntropy(const uint8_t*, size_t) const;
+
 private:
-  ESYS_CONTEXT* esys_;
+ TpmResourceManager& resource_manager_;
 };
 
 }  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_resource_manager.cpp b/host/commands/secure_env/tpm_resource_manager.cpp
index defe153..c177078 100644
--- a/host/commands/secure_env/tpm_resource_manager.cpp
+++ b/host/commands/secure_env/tpm_resource_manager.cpp
@@ -13,13 +13,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "tpm_resource_manager.h"
+#include "host/commands/secure_env/tpm_resource_manager.h"
+
+#include <mutex>
 
 #include <android-base/logging.h>
+#include <tss2/tss2_esys.h>
 #include <tss2/tss2_rc.h>
 
 namespace cuttlefish {
 
+EsysLock::EsysLock(ESYS_CONTEXT* esys, std::unique_lock<std::mutex> guard)
+    : esys_(esys), guard_(std::move(guard)) {}
+
 TpmResourceManager::ObjectSlot::ObjectSlot(TpmResourceManager* resource_manager)
     : ObjectSlot(resource_manager, ESYS_TR_NONE) {
 }
@@ -65,8 +71,8 @@
   }
 }
 
-ESYS_CONTEXT* TpmResourceManager::Esys() {
-  return esys_;
+EsysLock TpmResourceManager::Esys() {
+  return EsysLock(esys_, std::unique_lock<std::mutex>(mu_));
 }
 
 TpmObjectSlot TpmResourceManager::ReserveSlot() {
diff --git a/host/commands/secure_env/tpm_resource_manager.h b/host/commands/secure_env/tpm_resource_manager.h
index b5bf9ec..0713bfa 100644
--- a/host/commands/secure_env/tpm_resource_manager.h
+++ b/host/commands/secure_env/tpm_resource_manager.h
@@ -24,6 +24,19 @@
 
 namespace cuttlefish {
 
+class EsysLock {
+ public:
+  ESYS_CONTEXT* operator*() const { return esys_; }
+
+ private:
+  EsysLock(ESYS_CONTEXT*, std::unique_lock<std::mutex>);
+
+  ESYS_CONTEXT* esys_;
+  std::unique_lock<std::mutex> guard_;
+
+  friend class TpmResourceManager;
+};
+
 /**
  * Object slot manager for TPM memory. The TPM can only hold a fixed number of
  * objects at once. Some TPM operations are defined to consume slots either
@@ -54,14 +67,12 @@
   TpmResourceManager(ESYS_CONTEXT* esys);
   ~TpmResourceManager();
 
-  ESYS_CONTEXT* Esys();
+  // Returns a wrapped ESYS_CONTEXT* that can be used with Esys calls that also
+  // holds a lock. Callers should not hold onto the inner ESYS_CONTEXT* past the
+  // lifetime of the lock.
+  EsysLock Esys();
   std::shared_ptr<ObjectSlot> ReserveSlot();
 
-  // Return a lock guard to serialize access to the TPM.
-  std::lock_guard<std::mutex> Guard() {
-    return std::lock_guard<std::mutex>(mu_);
-  }
-
  private:
   std::mutex mu_;
   ESYS_CONTEXT* esys_;
diff --git a/host/commands/stop/main.cc b/host/commands/stop/main.cc
index 69449a6..206b580 100644
--- a/host/commands/stop/main.cc
+++ b/host/commands/stop/main.cc
@@ -269,6 +269,11 @@
      */
     return 134;
   }
-  cuttlefish::MetricsReceiver::LogMetricsVMStop();
+
+  if (cuttlefish::CuttlefishConfig::Get()->enable_metrics() ==
+      cuttlefish::CuttlefishConfig::Answer::kYes) {
+    cuttlefish::MetricsReceiver::LogMetricsVMStop();
+  }
+
   return cuttlefish::StopCvdMain(wait_for_launcher, clear_instance_dirs);
 }
diff --git a/host/cvd_test_configs/validation_failures/field_name_error.json b/host/cvd_test_configs/validation_failures/field_name_error.json
new file mode 100644
index 0000000..930fdf4
--- /dev/null
+++ b/host/cvd_test_configs/validation_failures/field_name_error.json
@@ -0,0 +1,18 @@
+{
+  "common": {
+    "host_package": "@ab/aosp-main/aosp_cf_x86_64_phone-trunk_staging-userdebug"
+  },
+  "instances": [
+    {
+      "@import": "phone",
+      "vm-typo": {
+        "memory_mb": 8192,
+        "setupwizard_mode": "OPTIONAL",
+        "cpus": 4
+      },
+      "disk-typo": {
+        "default_build": "@ab/git_main/cf_x86_64_phone-trunk_staging-userdebug"
+      }
+    }
+  ]
+}
diff --git a/host/cvd_test_configs/validation_failures/type_error.json b/host/cvd_test_configs/validation_failures/type_error.json
new file mode 100644
index 0000000..1d90710
--- /dev/null
+++ b/host/cvd_test_configs/validation_failures/type_error.json
@@ -0,0 +1,18 @@
+{
+  "common": {
+    "host_package": "@ab/aosp-main/aosp_cf_x86_64_phone-trunk_staging-userdebug"
+  },
+  "instances": [
+    {
+      "@import": "phone",
+      "vm": {
+        "memory_mb": "8192",
+        "setupwizard_mode": "OPTIONAL",
+        "cpus": "4"
+      },
+      "disk": {
+        "default_build": "@ab/git_main/cf_x86_64_phone-trunk_staging-userdebug"
+      }
+    }
+  ]
+}
diff --git a/host/frontend/webrtc/html_client/js/app.js b/host/frontend/webrtc/html_client/js/app.js
index 44395ea..912ccc2 100644
--- a/host/frontend/webrtc/html_client/js/app.js
+++ b/host/frontend/webrtc/html_client/js/app.js
@@ -947,6 +947,11 @@
   }
 
   #onKeyEvent(e) {
+    if (e.cancelable) {
+      // Some keyboard events cause unwanted side effects, like elements losing
+      // focus, if the default behavior is not prevented.
+      e.preventDefault();
+    }
     this.#deviceConnection.sendKeyEvent(e.code, e.type);
   }
 
diff --git a/host/libs/config/config_utils.cpp b/host/libs/config/config_utils.cpp
index df61b24..0ee9911 100644
--- a/host/libs/config/config_utils.cpp
+++ b/host/libs/config/config_utils.cpp
@@ -107,11 +107,24 @@
          file_name;
 }
 
+std::string HostBinaryDir() {
+  return DefaultHostArtifactsPath("bin");
+}
+
+std::string DefaultQemuBinaryDir() {
+  const std::string target_prod_str = StringFromEnv("TARGET_PRODUCT", "");
+  if (HostArch() == Arch::X86_64 &&
+      target_prod_str.find("arm") == std::string::npos) {
+    return HostBinaryDir();
+  }
+  return "/usr/bin";
+}
+
 std::string HostBinaryPath(const std::string& binary_name) {
 #ifdef __ANDROID__
   return binary_name;
 #else
-  return DefaultHostArtifactsPath("bin/" + binary_name);
+  return HostBinaryDir() + "/" + binary_name;
 #endif
 }
 
diff --git a/host/libs/config/config_utils.h b/host/libs/config/config_utils.h
index 58b3a69..1f9ca28 100644
--- a/host/libs/config/config_utils.h
+++ b/host/libs/config/config_utils.h
@@ -48,6 +48,7 @@
 std::string RandomSerialNumber(const std::string& prefix);
 
 std::string DefaultHostArtifactsPath(const std::string& file);
+std::string DefaultQemuBinaryDir();
 std::string HostBinaryPath(const std::string& file);
 std::string HostUsrSharePath(const std::string& file);
 std::string DefaultGuestImagePath(const std::string& file);
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index 71334f3..6d2b5a2 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -423,7 +423,14 @@
 
     std::string audio_server_path() const;
 
-    enum class BootFlow { Android, AndroidEfiLoader, ChromeOs, Linux, Fuchsia };
+    enum class BootFlow {
+      Android,
+      AndroidEfiLoader,
+      ChromeOs,
+      ChromeOsDisk,
+      Linux,
+      Fuchsia
+    };
 
     BootFlow boot_flow() const;
 
@@ -623,8 +630,9 @@
 
     // android efi loader flow
     std::string android_efi_loader() const;
-    //
-    // linux artifacts for otheros flow
+
+    // chromeos artifacts for otheros flow
+    std::string chromeos_disk() const;
     std::string chromeos_kernel_path() const;
     std::string chromeos_root_image() const;
 
@@ -818,6 +826,7 @@
     void set_system_target_zip(const std::string& system_target_zip);
     void set_otheros_esp_image(const std::string& otheros_esp_image);
     void set_android_efi_loader(const std::string& android_efi_loader);
+    void set_chromeos_disk(const std::string& chromeos_disk);
     void set_chromeos_kernel_path(const std::string& linux_kernel_path);
     void set_chromeos_root_image(const std::string& linux_root_image);
     void set_linux_kernel_path(const std::string& linux_kernel_path);
@@ -946,3 +955,6 @@
 extern const char* const kHwComposerRanchu;
 extern const char* const kHwComposerNone;
 }  // namespace cuttlefish
+
+template <>
+struct fmt::formatter<cuttlefish::ExternalNetworkMode> : ostream_formatter {};
diff --git a/host/libs/config/cuttlefish_config_instance.cpp b/host/libs/config/cuttlefish_config_instance.cpp
index 51da6d9..04d287a 100644
--- a/host/libs/config/cuttlefish_config_instance.cpp
+++ b/host/libs/config/cuttlefish_config_instance.cpp
@@ -254,6 +254,14 @@
     const std::string& android_efi_loader) {
   (*Dictionary())[kAndroidEfiLoader] = android_efi_loader;
 }
+static constexpr char kChromeOsDisk[] = "chromeos_disk";
+std::string CuttlefishConfig::InstanceSpecific::chromeos_disk() const {
+  return (*Dictionary())[kChromeOsDisk].asString();
+}
+void CuttlefishConfig::MutableInstanceSpecific::set_chromeos_disk(
+    const std::string& chromeos_disk) {
+  (*Dictionary())[kChromeOsDisk] = chromeos_disk;
+}
 static constexpr char kChromeOsKernelPath[] = "chromeos_kernel_path";
 std::string CuttlefishConfig::InstanceSpecific::chromeos_kernel_path() const {
   return (*Dictionary())[kChromeOsKernelPath].asString();
@@ -1238,6 +1246,8 @@
 CuttlefishConfig::InstanceSpecific::BootFlow CuttlefishConfig::InstanceSpecific::boot_flow() const {
   const bool android_efi_loader_flow_used = !android_efi_loader().empty();
 
+  const bool chromeos_disk_flow_used = !chromeos_disk().empty();
+
   const bool chromeos_flow_used =
       !chromeos_kernel_path().empty() || !chromeos_root_image().empty();
 
@@ -1253,6 +1263,8 @@
     return BootFlow::AndroidEfiLoader;
   } else if (chromeos_flow_used) {
     return BootFlow::ChromeOs;
+  } else if (chromeos_disk_flow_used) {
+    return BootFlow::ChromeOsDisk;
   } else if (linux_flow_used) {
     return BootFlow::Linux;
   } else if (fuchsia_flow_used) {
diff --git a/host/libs/config/known_paths.cpp b/host/libs/config/known_paths.cpp
index d98c093..b50cc19 100644
--- a/host/libs/config/known_paths.cpp
+++ b/host/libs/config/known_paths.cpp
@@ -78,6 +78,10 @@
 
 std::string CasimirBinary() { return HostBinaryPath("casimir"); }
 
+std::string ScreenRecordingServerBinary() {
+  return HostBinaryPath("screen_recording_server");
+}
+
 std::string SecureEnvBinary() { return HostBinaryPath("secure_env"); }
 
 std::string SocketVsockProxyBinary() {
diff --git a/host/libs/config/known_paths.h b/host/libs/config/known_paths.h
index 1ec7515..4952227 100644
--- a/host/libs/config/known_paths.h
+++ b/host/libs/config/known_paths.h
@@ -36,6 +36,7 @@
 std::string ProcessRestarterBinary();
 std::string RootCanalBinary();
 std::string CasimirBinary();
+std::string ScreenRecordingServerBinary();
 std::string SecureEnvBinary();
 std::string SocketVsockProxyBinary();
 std::string StopCvdBinary();
diff --git a/shared/BoardConfig.mk b/shared/BoardConfig.mk
index 2281b1d..094d74c 100644
--- a/shared/BoardConfig.mk
+++ b/shared/BoardConfig.mk
@@ -225,6 +225,7 @@
 # enough space for other cases (such as remount, etc)
 BOARD_USERDATAIMAGE_PARTITION_SIZE := $(TARGET_USERDATAIMAGE_PARTITION_SIZE)
 BOARD_USERDATAIMAGE_FILE_SYSTEM_TYPE := $(TARGET_USERDATAIMAGE_FILE_SYSTEM_TYPE)
+$(call soong_config_append,cvdhost,default_userdata_fs_type,$(TARGET_USERDATAIMAGE_FILE_SYSTEM_TYPE))
 ifeq ($(TARGET_USERDATAIMAGE_FILE_SYSTEM_TYPE),f2fs)
 TARGET_USERIMAGES_USE_F2FS := true
 endif
@@ -424,7 +425,3 @@
 ifneq ($(PRODUCT_BUILD_VBMETA_IMAGE), false)
 AB_OTA_PARTITIONS += vbmeta
 endif
-
-ifeq ($(TARGET_ARCH),arm64)
-$(call soong_config_append,cvdhost,vhost_user_vsock_by_default,true)
-endif
\ No newline at end of file
diff --git a/shared/auto/device_vendor.mk b/shared/auto/device_vendor.mk
index 1c134e8..08712a4 100644
--- a/shared/auto/device_vendor.mk
+++ b/shared/auto/device_vendor.mk
@@ -45,6 +45,9 @@
         frameworks/native/data/etc/android.hardware.sensor.compass.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.compass.xml
 endif
 
+PRODUCT_PRODUCT_PROPERTIES += \
+    ro.boot.uwbcountrycode=US
+
 PRODUCT_COPY_FILES += \
     frameworks/native/data/etc/car_core_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/car_core_hardware.xml \
     frameworks/native/data/etc/android.hardware.broadcastradio.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.broadcastradio.xml \
diff --git a/shared/bluetooth/device_vendor.mk b/shared/bluetooth/device_vendor.mk
index b8b4554..451f300 100644
--- a/shared/bluetooth/device_vendor.mk
+++ b/shared/bluetooth/device_vendor.mk
@@ -30,29 +30,7 @@
 PRODUCT_COPY_FILES += \
     frameworks/av/services/audiopolicy/config/bluetooth_audio_policy_configuration_7_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/bluetooth_audio_policy_configuration_7_0.xml \
 
-#
-# Bluetooth HAL and Compatibility Bluetooth library (for older revs).
-#
-ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
-PRODUCT_COPY_FILES +=\
-    frameworks/native/data/etc/android.hardware.bluetooth.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.bluetooth.xml \
-    frameworks/native/data/etc/android.hardware.bluetooth_le.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.bluetooth_le.xml
-
-PRODUCT_PACKAGES += \
-    android.hardware.bluetooth-service.default \
-    android.hardware.bluetooth.finder-service.default \
-    android.hardware.bluetooth.ranging-service.default \
-    bt_vhci_forwarder
-
-# Bluetooth initialization configuration is copied to the init folder here instead of being added
-# as an init_rc attribute of the bt_vhci_forward binary.  The bt_vhci_forward binary is used by
-# multiple targets with different initialization configurations.
-PRODUCT_COPY_FILES += \
-    device/google/cuttlefish/guest/commands/bt_vhci_forwarder/bt_vhci_forwarder.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/bt_vhci_forwarder.rc
-
-else
 PRODUCT_PACKAGES += com.google.cf.bt
-endif
 
 #
 # Bluetooth Audio AIDL HAL
diff --git a/shared/config/input/Crosvm_Virtio_Multitouch_Touchpad_0.idc b/shared/config/input/Crosvm_Virtio_Multitouch_Touchpad_0.idc
index 18868dc..1d88567 100644
--- a/shared/config/input/Crosvm_Virtio_Multitouch_Touchpad_0.idc
+++ b/shared/config/input/Crosvm_Virtio_Multitouch_Touchpad_0.idc
@@ -2,3 +2,6 @@
 
 touch.deviceType = touchPad
 touch.orientationAware = 0
+
+# Allow touches while the screen is off
+touch.enableForInactiveViewport = 1
diff --git a/shared/device.mk b/shared/device.mk
index 962a34b..30ad25d 100644
--- a/shared/device.mk
+++ b/shared/device.mk
@@ -87,7 +87,7 @@
 # Explanation of specific properties:
 #   ro.hardware.keystore_desede=true needed for CtsKeystoreTestCases
 PRODUCT_VENDOR_PROPERTIES += \
-    tombstoned.max_tombstone_count=500 \
+    tombstoned.max_tombstone_count=100 \
     ro.carrier=unknown \
     ro.com.android.dataroaming?=false \
     ro.hardware.virtual_device=1 \
@@ -180,13 +180,7 @@
     hidl_lazy_cb_test_server
 
 # Runtime Resource Overlays
-ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
-PRODUCT_PACKAGES += \
-    cuttlefish_overlay_connectivity \
-    cuttlefish_overlay_frameworks_base_core \
-    cuttlefish_overlay_settings_provider
-
-endif
+PRODUCT_PACKAGES += com.google.aosp_cf.rros
 
 #
 # Satellite vendor service for CF
@@ -310,6 +304,7 @@
     libpreprocessingaidl \
     libpresetreverbsw \
     libreverbaidl \
+    libspatializersw \
     libtinyxml2 \
     libvirtualizersw \
     libvisualizeraidl \
@@ -623,3 +618,5 @@
 ifeq ($(RELEASE_DEPRECATE_VNDK),true)
 KEEP_VNDK ?= false
 endif
+
+TARGET_BOARD_FASTBOOT_INFO_FILE = device/google/cuttlefish/shared/fastboot-info.txt
\ No newline at end of file
diff --git a/shared/fastboot-info.txt b/shared/fastboot-info.txt
new file mode 100644
index 0000000..0448334
--- /dev/null
+++ b/shared/fastboot-info.txt
@@ -0,0 +1,14 @@
+# cuttlefish
+version 1
+flash boot
+flash init_boot
+flash vendor_boot
+flash --apply-vbmeta vbmeta
+flash vbmeta_system
+flash vbmeta_vendor_dlkm
+flash vbmeta_system_dlkm
+reboot fastboot
+update-super
+flash super
+if-wipe erase userdata
+if-wipe erase metadata
\ No newline at end of file
diff --git a/shared/phone/device_vendor.mk b/shared/phone/device_vendor.mk
index e7f1276..e22a22f 100644
--- a/shared/phone/device_vendor.mk
+++ b/shared/phone/device_vendor.mk
@@ -59,11 +59,7 @@
 DEVICE_PACKAGE_OVERLAYS += device/google/cuttlefish/shared/phone/overlay
 
 # Runtime Resource Overlays
-ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
-PRODUCT_PACKAGES += com.google.aosp_cf_phone.rros
-else
 PRODUCT_PACKAGES += cuttlefish_phone_overlay_frameworks_base_core
-endif
 
 # NFC AIDL HAL
 PRODUCT_PACKAGES += \
diff --git a/shared/phone/vendor.prop b/shared/phone/vendor.prop
index 5bb745a..8157fa0 100644
--- a/shared/phone/vendor.prop
+++ b/shared/phone/vendor.prop
@@ -5,4 +5,5 @@
 bluetooth.profile.ccp.server.enabled=true
 bluetooth.profile.csip.set_coordinator.enabled=true
 bluetooth.profile.hap.client.enabled=true
+bluetooth.profile.mcp.server.enabled=true
 bluetooth.profile.vcp.controller.enabled=true
diff --git a/shared/sensors/device_vendor.mk b/shared/sensors/device_vendor.mk
index 73c1c5e..f497fb9 100644
--- a/shared/sensors/device_vendor.mk
+++ b/shared/sensors/device_vendor.mk
@@ -14,11 +14,24 @@
 # limitations under the License.
 #
 
+# set LOCAL_SENSOR_FILE_OVERRIDES := true if a device has a custom list of sensors. Otherwise
+# install the default set like below
+ifneq ($(LOCAL_SENSOR_FILE_OVERRIDES),true)
+    PRODUCT_COPY_FILES += \
+        frameworks/native/data/etc/android.hardware.sensor.ambient_temperature.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.ambient_temperature.xml \
+        frameworks/native/data/etc/android.hardware.sensor.barometer.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.barometer.xml \
+        frameworks/native/data/etc/android.hardware.sensor.gyroscope.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.gyroscope.xml \
+        frameworks/native/data/etc/android.hardware.sensor.hinge_angle.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.hinge_angle.xml \
+        frameworks/native/data/etc/android.hardware.sensor.light.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.light.xml \
+        frameworks/native/data/etc/android.hardware.sensor.proximity.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.proximity.xml \
+        frameworks/native/data/etc/android.hardware.sensor.relative_humidity.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.relative_humidity.xml
+endif
+
 PRODUCT_SOONG_NAMESPACES += device/google/cuttlefish/shared/sensors/multihal
 
-#
-# Sensors
-#
+# Set LOCAL_SENSOR_PRODUCT_PACKAGE := <package list> if a device wants to install custom implementations
+# Should check if the default feature list is okay with the implementation. Otherwise, it should set
+# LOCAL_SENSOR_FILE_OVERRIDES and copy feature files.
 ifeq ($(LOCAL_SENSOR_PRODUCT_PACKAGE),)
        LOCAL_SENSOR_PRODUCT_PACKAGE := com.android.hardware.sensors
 endif
diff --git a/shared/sepolicy/system_ext/private/hal_audio_parser_service.te b/shared/sepolicy/system_ext/private/hal_audio_parser_service.te
new file mode 100644
index 0000000..0a68d01
--- /dev/null
+++ b/shared/sepolicy/system_ext/private/hal_audio_parser_service.te
@@ -0,0 +1,2 @@
+# Currently this is a stub. This service is only actually implemented by SoC vendors.
+allow audioserver hal_audio_vendor_parameter_parser_service:service_manager find;
diff --git a/shared/sepolicy/system_ext/private/service.te b/shared/sepolicy/system_ext/private/service.te
new file mode 100644
index 0000000..50e7415
--- /dev/null
+++ b/shared/sepolicy/system_ext/private/service.te
@@ -0,0 +1 @@
+type hal_audio_vendor_parameter_parser_service, service_manager_type;
diff --git a/shared/sepolicy/system_ext/private/service_contexts b/shared/sepolicy/system_ext/private/service_contexts
new file mode 100644
index 0000000..afee8ca
--- /dev/null
+++ b/shared/sepolicy/system_ext/private/service_contexts
@@ -0,0 +1 @@
+android.media.audio.IHalAdapterVendorExtension/default               u:object_r:hal_audio_vendor_parameter_parser_service:s0
diff --git a/shared/slim/device_vendor.mk b/shared/slim/device_vendor.mk
index 127ac4a..e980edf 100644
--- a/shared/slim/device_vendor.mk
+++ b/shared/slim/device_vendor.mk
@@ -48,16 +48,9 @@
     frameworks/native/data/etc/android.hardware.faketouch.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.faketouch.xml \
     frameworks/native/data/etc/android.hardware.fingerprint.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.fingerprint.xml \
 
-
 # Runtime Resource Overlays
-ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
-PRODUCT_PACKAGES += \
-    com.google.aosp_cf_phone.rros \
-    com.google.aosp_cf_slim.rros
-else
 PRODUCT_PACKAGES += \
     cuttlefish_phone_overlay_frameworks_base_core \
     slim_overlay_frameworks_base_core
-endif
 
 TARGET_BOARD_INFO_FILE ?= device/google/cuttlefish/shared/slim/android-info.txt
diff --git a/shared/telephony/device_vendor.mk b/shared/telephony/device_vendor.mk
index bd78cc6..954076e 100644
--- a/shared/telephony/device_vendor.mk
+++ b/shared/telephony/device_vendor.mk
@@ -27,21 +27,10 @@
     ro.com.android.dataroaming=true \
     ro.telephony.default_network=9 \
 
-ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
-PRODUCT_PACKAGES += com.google.cf.rild
-else
 # If downstream target provides its own RILD, set TARGET_USES_CF_RILD := false
-# If the target prefers vendor APEX, this feature is not supported
 TARGET_USES_CF_RILD ?= true
 ifeq ($(TARGET_USES_CF_RILD),true)
-PRODUCT_PACKAGES += \
-    libcuttlefish-ril-2 \
-    libcuttlefish-rild
+    PRODUCT_PACKAGES += com.google.cf.rild
 endif
-PRODUCT_COPY_FILES += \
-    frameworks/native/data/etc/android.hardware.telephony.gsm.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.telephony.gsm.xml \
-    frameworks/native/data/etc/android.hardware.telephony.ims.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.telephony.ims.xml \
-    frameworks/native/data/etc/android.hardware.telephony.satellite.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.telephony.satellite.xml
-endif # if not LOCAL_PREFER_VENDOR_APEX
 
-endif # if not TARGET_NO_TELEPHONY
+endif # if not TARGET_NO_TELEPHONY
\ No newline at end of file
diff --git a/tests/snapshot/src/com/android/cuttlefish/tests/SnapshotTest.java b/tests/snapshot/src/com/android/cuttlefish/tests/SnapshotTest.java
index 668cb52..cecf83b 100644
--- a/tests/snapshot/src/com/android/cuttlefish/tests/SnapshotTest.java
+++ b/tests/snapshot/src/com/android/cuttlefish/tests/SnapshotTest.java
@@ -75,6 +75,7 @@
       // validating the feature itself so it's fine
       boolean restoreRes = false;
       try {
+        handler = new DeviceSnapshotHandler();
         restoreRes = handler.restoreSnapshotDevice(getDevice(), String.format("snapshot_img%d", mTestCount));
       } catch (DeviceNotAvailableException e) {
         CLog.e(e);
diff --git a/tools/cvd_uncolor_text.sh b/tools/cvd_uncolor_text.sh
new file mode 100755
index 0000000..90188ed
--- /dev/null
+++ b/tools/cvd_uncolor_text.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+sed -E 's|[^[:alnum:]]\[[^m]+m||g' "$@"
diff --git a/tools/launch_cvd_arm64_server.sh b/tools/launch_cvd_arm64_server.sh
index 6c429bb..60c7a49 100755
--- a/tools/launch_cvd_arm64_server.sh
+++ b/tools/launch_cvd_arm64_server.sh
@@ -87,10 +87,13 @@
 
 # sets up SSH port forwarding to the remote server for various ports and launch cvd instance
 adb_port_forwarding=""
+print_launcher_logs=""
 for instance_num in $(seq $base_instance_num $(($base_instance_num+$num_instances-1))); do
+  device_name="cvd_$base_instance_num-$instance_num"
   adb_port=$((6520+$instance_num-1))
-  echo -e "Device-$instance_num is using adb port $adb_port. Try ${color_cyan}adb connect 127.0.0.1:${adb_port}${color_plain} if you want to connect to this device"
+  echo -e "$device_name is using adb port $adb_port. Try ${color_cyan}adb connect 127.0.0.1:${adb_port}${color_plain} if you want to connect to this device"
   adb_port_forwarding+="-L $adb_port:127.0.0.1:$adb_port "
+  print_launcher_logs+="tail -f ~/$cvd_home_dir/cuttlefish/instances/cvd-$instance_num/logs/launcher.log | sed 's/^/[$device_name] /' &"
 done
 
 ports_forwarding="-L $web_ui_port:127.0.0.1:1443 \
@@ -100,4 +103,4 @@
   $adb_port_forwarding"
 echo "Set up ssh ports forwarding: $ports_forwarding"
 echo -e "${color_yellow}Please stop the running instances by ctrl+c${color_plain}"
-ssh -N $server $ports_forwarding
+ssh $server $ports_forwarding $print_launcher_logs
diff --git a/vsoc_arm64_pgagnostic/BoardConfig.mk b/vsoc_arm64_pgagnostic/BoardConfig.mk
index 5a2773a..5ae28c4 100644
--- a/vsoc_arm64_pgagnostic/BoardConfig.mk
+++ b/vsoc_arm64_pgagnostic/BoardConfig.mk
@@ -44,6 +44,7 @@
 HOST_CROSS_2ND_ARCH :=
 
 -include device/google/cuttlefish/shared/BoardConfig.mk
+-include device/google/cuttlefish/shared/bluetooth/BoardConfig.mk
 -include device/google/cuttlefish/shared/camera/BoardConfig.mk
 -include device/google/cuttlefish/shared/graphics/BoardConfig.mk
 -include device/google/cuttlefish/shared/identity/BoardConfig.mk
diff --git a/vsoc_x86_64_pgagnostic/BoardConfig.mk b/vsoc_x86_64_pgagnostic/BoardConfig.mk
index b8f2d1d..a9870f6 100644
--- a/vsoc_x86_64_pgagnostic/BoardConfig.mk
+++ b/vsoc_x86_64_pgagnostic/BoardConfig.mk
@@ -28,6 +28,9 @@
 TARGET_NATIVE_BRIDGE_CPU_VARIANT := generic
 TARGET_NATIVE_BRIDGE_ABI := arm64-v8a
 
+TARGET_USERDATAIMAGE_FILE_SYSTEM_TYPE := ext4
+TARGET_RO_FILE_SYSTEM_TYPE := ext4
+
 AUDIOSERVER_MULTILIB := first
 
 -include device/google/cuttlefish/shared/BoardConfig.mk