[automerge] radio: provide hardcoded IMEI and null IMEI-SV 2p: 5154019c1f am: fa19a971a2 am: 0c87c389f4

Original change: https://googleplex-android-review.googlesource.com/c/device/google/cuttlefish/+/18620247

Change-Id: Ia845081caaef3a81cb6912564f1de80ddeb9a692
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.clang-format b/.clang-format
index 9f9ffe3..a03f8b3 100644
--- a/.clang-format
+++ b/.clang-format
@@ -19,3 +19,4 @@
 # of the below options.
 
 BasedOnStyle: Google
+IncludeBlocks: Preserve
diff --git a/Android.mk b/Android.mk
index 59e9806..78b607f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -11,7 +11,41 @@
 # 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.
-ifneq ($(filter vsoc_arm64 vsoc_x86 vsoc_x86_64, $(TARGET_BOARD_PLATFORM)),)
+
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,.idc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,default-permissions.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,libnfc-nci.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,fstab.postinstall,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,ueventd.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,hals.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,device_state_configuration.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,task_profiles.json,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,p2p_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(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.f2fs,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,fstab.ext4,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,init.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish,audio_policy.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,))
+
+$(eval $(call declare-copy-files-license-metadata,device/google/cuttlefish/shared/config,pci.ids,SPDX-license-identifier-BSD-3-Clause,notice,device/google/cuttlefish/shared/config/LICENSE_BSD,))
+
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,privapp-permissions-cuttlefish.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,media_profiles_V1_0.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,media_codecs_performance.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,cuttlefish_excluded_hardware.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,media_codecs.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,media_codecs_google_video.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,car_audio_configuration.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,audio_policy_configuration.xml))
+$(eval $(call declare-1p-copy-files,device/google/cuttlefish,preinstalled-packages-product-car-cuttlefish.xml))
+$(eval $(call declare-1p-copy-files,hardware/google/camera/devices,.json))
+
+ifneq ($(filter vsoc_arm vsoc_arm64 vsoc_x86 vsoc_x86_64, $(TARGET_BOARD_PLATFORM)),)
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
diff --git a/AndroidProducts.mk b/AndroidProducts.mk
index 8d06ed7..d345316 100644
--- a/AndroidProducts.mk
+++ b/AndroidProducts.mk
@@ -16,24 +16,26 @@
 
 PRODUCT_MAKEFILES := \
 	aosp_cf_arm_only_phone:$(LOCAL_DIR)/vsoc_arm_only/phone/aosp_cf.mk \
-	aosp_cf_arm64_auto:$(LOCAL_DIR)/vsoc_arm64/auto/aosp_cf.mk \
+	aosp_cf_arm64_auto:$(LOCAL_DIR)/vsoc_arm64_only/auto/aosp_cf.mk \
 	aosp_cf_arm64_phone:$(LOCAL_DIR)/vsoc_arm64/phone/aosp_cf.mk \
+	aosp_cf_arm64_phone_hwasan:$(LOCAL_DIR)/vsoc_arm64/phone/aosp_cf_hwasan.mk \
 	aosp_cf_arm64_only_phone:$(LOCAL_DIR)/vsoc_arm64_only/phone/aosp_cf.mk \
-	aosp_cf_x86_64_auto:$(LOCAL_DIR)/vsoc_x86_64/auto/device.mk \
+	aosp_cf_arm64_only_phone_hwasan:$(LOCAL_DIR)/vsoc_arm64_only/phone/aosp_cf_hwasan.mk \
+	aosp_cf_arm64_slim:$(LOCAL_DIR)/vsoc_arm64_only/slim/aosp_cf.mk \
+	aosp_cf_x86_64_auto:$(LOCAL_DIR)/vsoc_x86_64/auto/aosp_cf.mk \
 	aosp_cf_x86_64_pc:$(LOCAL_DIR)/vsoc_x86_64/pc/aosp_cf.mk \
 	aosp_cf_x86_64_phone:$(LOCAL_DIR)/vsoc_x86_64/phone/aosp_cf.mk \
-	aosp_cf_x86_64_tv:$(LOCAL_DIR)/vsoc_x86_64/tv/device.mk \
+	aosp_cf_x86_64_tv:$(LOCAL_DIR)/vsoc_x86_64/tv/aosp_cf.mk \
 	aosp_cf_x86_64_foldable:$(LOCAL_DIR)/vsoc_x86_64/phone/aosp_cf_foldable.mk \
 	aosp_cf_x86_64_only_phone:$(LOCAL_DIR)/vsoc_x86_64_only/phone/aosp_cf.mk \
-	aosp_cf_x86_auto:$(LOCAL_DIR)/vsoc_x86/auto/device.mk \
+	aosp_cf_x86_64_slim:$(LOCAL_DIR)/vsoc_x86_64_only/slim/aosp_cf.mk \
+	aosp_cf_x86_auto:$(LOCAL_DIR)/vsoc_x86/auto/aosp_cf.mk \
 	aosp_cf_x86_pasan:$(LOCAL_DIR)/vsoc_x86/pasan/aosp_cf.mk \
 	aosp_cf_x86_phone:$(LOCAL_DIR)/vsoc_x86/phone/aosp_cf.mk \
-	aosp_cf_x86_phone_noapex:$(LOCAL_DIR)/vsoc_x86_noapex/aosp_cf_noapex.mk \
 	aosp_cf_x86_only_phone:$(LOCAL_DIR)/vsoc_x86_only/phone/aosp_cf.mk \
-	aosp_cf_x86_go_phone:$(LOCAL_DIR)/vsoc_x86/go_phone/device.mk \
-	aosp_cf_x86_go_512_phone:$(LOCAL_DIR)/vsoc_x86/go_512_phone/device.mk \
-	aosp_cf_x86_tv:$(LOCAL_DIR)/vsoc_x86/tv/device.mk
-
+	aosp_cf_x86_go_phone:$(LOCAL_DIR)/vsoc_x86/go/aosp_cf.mk \
+	aosp_cf_x86_tv:$(LOCAL_DIR)/vsoc_x86/tv/aosp_cf.mk \
+	aosp_cf_x86_wear:$(LOCAL_DIR)/vsoc_x86/wear/aosp_cf.mk \
 
 COMMON_LUNCH_CHOICES := \
 	aosp_cf_arm64_auto-userdebug \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index f235841..6d10d12 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -58,6 +58,9 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/android.hardware.keymaster@4.0-service)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/android.hardware.keymaster@4.0-service.rc)
 
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/android.hardware.confirmationui@1.0-service)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/android.hardware.confirmationui@1.0-service.rc)
+
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/android.hardware.power@1.0-service.rc)
 
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/bin/hw/android.hardware.dumpstate@1.1-service.cuttlefish)
@@ -68,3 +71,8 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/vintf/manifest/manifest_android.hardware.health.storage@1.0.cuttlefish.xml)
 
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/apex/com.android.gki.*)
+
+$(call add-clean-step, find $(PRODUCT_OUT)/system -type f -name "*charger*" -print0 | xargs -0 rm -f)
+$(call add-clean-step, find $(PRODUCT_OUT)/vendor -type f -name "*health@*" -print0 | xargs -0 rm -f)
+$(call add-clean-step, find $(PRODUCT_OUT)/recovery/root -type f -name "*charger*" -print0 | xargs -0 rm -f)
+$(call add-clean-step, find $(PRODUCT_OUT)/recovery/root -type f -name "*health@*" -print0 | xargs -0 rm -f)
diff --git a/README.md b/README.md
index 2220898..61c9987 100644
--- a/README.md
+++ b/README.md
@@ -5,22 +5,33 @@
 1. Make sure virtualization with KVM is available.
 
    ```bash
-    grep -c -w "vmx\|svm" /proc/cpuinfo
-    ```
+   grep -c -w "vmx\|svm" /proc/cpuinfo
+   ```
 
    This should return a non-zero value. If running on a cloud machine, this may
    take cloud-vendor-specific steps to enable. For Google Compute Engine
    specifically, see the [GCE guide].
 
-   [GCE guide]: https://cloud.google.com/compute/docs/instances/enable-nested-virtualization-vm-instances
+  [GCE guide]: https://cloud.google.com/compute/docs/instances/enable-nested-virtualization-vm-instances
+
+*** promo
+   ARM specific steps:
+   - When running on an ARM machine, the most direct way is to check
+   for the existence of `/dev/kvm`. Note that this method can also be used to
+   confirm support of KVM on any environment.
+   - Before proceeding to the next step, please first follow
+   [the guide](multiarch-howto.md) to adjust APT sources.
+***
 
 2. Download, build, and install the host debian package:
 
    ```bash
+   sudo apt install -y git devscripts config-package-dev debhelper-compat golang
    git clone https://github.com/google/android-cuttlefish
    cd android-cuttlefish
-   debuild -i -us -uc -b
-   sudo dpkg -i ../cuttlefish-common_*_amd64.deb || sudo apt-get install -f
+   debuild -i -us -uc -b -d
+   sudo dpkg -i ../cuttlefish-common_*_*64.deb || sudo apt-get install -f
+   sudo usermod -aG kvm,cvdnetwork,render $USER
    sudo reboot
    ```
 
@@ -31,6 +42,11 @@
 4. Enter a branch name. Start with `aosp-master` if you don't know what you're
    looking for
 5. Navigate to `aosp_cf_x86_64_phone` and click on `userdebug` for the latest build
+
+*** promo
+   For ARM, use branch `aosp-master-throttled-copped` and device target `aosp_cf_arm64_only_phone-userdebug`
+***
+
 6. Click on `Artifacts`
 7. Scroll down to the OTA images. These packages look like
    `aosp_cf_x86_64_phone-img-xxxxxx.zip` -- it will always have `img` in the name.
@@ -50,10 +66,6 @@
 
    `$ HOME=$PWD ./bin/launch_cvd`
 
-11. Stop cuttlefish with:
-
-   `$ HOME=$PWD ./bin/stop_cvd`
-
 ## Debug Cuttlefish
 
 You can use `adb` to debug it, just like a physical device:
@@ -67,12 +79,10 @@
 WebRTC on Cuttlefish
 [documentation](https://source.android.com/setup/create/cuttlefish-ref-webrtc).
 
-## Launch Viewer (VNC)
+## Stop Cuttlefish
 
-When launching with `--start_vnc_server=true` , You can use the
-[TightVNC JViewer](https://www.tightvnc.com/download.php). Once you have
-downloaded the *TightVNC Java Viewer JAR in a ZIP archive*, run it with
+You will need to stop the virtual device within the same directory as you used
+to launch the device.
 
-   `$ java -jar tightvnc-jviewer.jar -ScalingFactor=50 -Tunneling=no -host=localhost -port=6444`
+    `$ HOME=$PWD ./bin/stop_cvd`
 
-Click "Connect" and you should see a lock screen!
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4067d27..92e1712 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -19,6 +19,15 @@
     },
     {
       "name": "vts_ibase_test"
+    },
+    {
+      "name": "OverlayDeviceTests"
+    },
+    {
+      "name": "CtsNativeVerifiedBootTestCases"
+    },
+    {
+      "name": "CtsScopedStorageDeviceOnlyTest"
     }
   ]
 }
diff --git a/apex/com.google.aosp_cf_phone.hardware.core_permissions/Android.bp b/apex/com.google.aosp_cf_phone.hardware.core_permissions/Android.bp
new file mode 100644
index 0000000..2900f32
--- /dev/null
+++ b/apex/com.google.aosp_cf_phone.hardware.core_permissions/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+override_apex {
+    name: "com.google.aosp_cf_phone.hardware.core_permissions",
+    base: "com.android.hardware.core_permissions",
+    prebuilts: [
+        "android.hardware.audio.low_latency.prebuilt.xml",
+        "android.hardware.biometrics.face.prebuilt.xml",
+        "android.hardware.ethernet.prebuilt.xml",
+        "android.hardware.faketouch.prebuilt.xml",
+        "android.hardware.fingerprint.prebuilt.xml",
+        "android.hardware.location.gps.prebuilt.xml",
+        "android.hardware.reboot_escrow.prebuilt.xml",
+        "android.hardware.usb.accessory.prebuilt.xml",
+        "android.hardware.usb.host.prebuilt.xml",
+        "android.hardware.vulkan.level-0.prebuilt.xml",
+        "android.hardware.vulkan.version-1_0_3.prebuilt.xml",
+        "android.software.device_id_attestation.prebuilt.xml",
+        "android.software.ipsec_tunnels.prebuilt.xml",
+        "android.software.opengles.deqp.level-2022-03-01.prebuilt.xml",
+        "android.software.sip.voip.prebuilt.xml",
+        "android.software.verified_boot.prebuilt.xml",
+        "android.software.vulkan.deqp.level-2022-03-01.prebuilt.xml",
+        "aosp_excluded_hardware.prebuilt.xml",
+        "cuttlefish_excluded_hardware.prebuilt.xml",
+        "handheld_core_hardware.prebuilt.xml",
+    ],
+}
diff --git a/apex/com.google.aosp_cf_phone.rros/Android.bp b/apex/com.google.aosp_cf_phone.rros/Android.bp
new file mode 100644
index 0000000..325e1b3
--- /dev/null
+++ b/apex/com.google.aosp_cf_phone.rros/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex {
+    name: "com.google.aosp_cf_phone.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: [
+        "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_phone.rros/apex_manifest.json b/apex/com.google.aosp_cf_phone.rros/apex_manifest.json
new file mode 100644
index 0000000..e5ebe27
--- /dev/null
+++ b/apex/com.google.aosp_cf_phone.rros/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+  "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
new file mode 100644
index 0000000..cb7fd8d
--- /dev/null
+++ b/apex/com.google.aosp_cf_phone.rros/file_contexts
@@ -0,0 +1,2 @@
+(/.*)?		u:object_r:vendor_file:s0
+/overlay(/.*)?	u:object_r:vendor_overlay_file:s0
diff --git a/apex/com.google.aosp_cf_slim.hardware.core_permissions/Android.bp b/apex/com.google.aosp_cf_slim.hardware.core_permissions/Android.bp
new file mode 100644
index 0000000..959c950
--- /dev/null
+++ b/apex/com.google.aosp_cf_slim.hardware.core_permissions/Android.bp
@@ -0,0 +1,45 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+override_apex {
+    name: "com.google.aosp_cf_slim.hardware.core_permissions",
+    base: "com.android.hardware.core_permissions",
+    prebuilts: [
+        "android.hardware.audio.low_latency.prebuilt.xml",
+        "android.hardware.biometrics.face.prebuilt.xml",
+        "android.hardware.ethernet.prebuilt.xml",
+        "android.hardware.faketouch.prebuilt.xml",
+        "android.hardware.fingerprint.prebuilt.xml",
+        "android.hardware.location.gps.prebuilt.xml",
+        "android.hardware.reboot_escrow.prebuilt.xml",
+        "android.hardware.usb.accessory.prebuilt.xml",
+        "android.hardware.usb.host.prebuilt.xml",
+        "android.hardware.vulkan.level-0.prebuilt.xml",
+        "android.hardware.vulkan.version-1_0_3.prebuilt.xml",
+        "android.software.device_id_attestation.prebuilt.xml",
+        "android.software.ipsec_tunnels.prebuilt.xml",
+        "android.software.opengles.deqp.level-2022-03-01.prebuilt.xml",
+        "android.software.sip.voip.prebuilt.xml",
+        "android.software.verified_boot.prebuilt.xml",
+        "android.software.vulkan.deqp.level-2022-03-01.prebuilt.xml",
+        "aosp_excluded_hardware.prebuilt.xml",
+        "cuttlefish_excluded_hardware.prebuilt.xml",
+        "handheld_core_hardware.prebuilt.xml",
+        "slim_excluded_hardware.prebuilt.xml",
+    ],
+}
diff --git a/apex/com.google.aosp_cf_slim.rros/Android.bp b/apex/com.google.aosp_cf_slim.rros/Android.bp
new file mode 100644
index 0000000..1a98d1c
--- /dev/null
+++ b/apex/com.google.aosp_cf_slim.rros/Android.bp
@@ -0,0 +1,38 @@
+// 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
new file mode 100644
index 0000000..26c7bd5
--- /dev/null
+++ b/apex/com.google.aosp_cf_slim.rros/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.google.aosp_cf_slim.rros",
+  "version": 1
+}
diff --git a/apex/com.google.aosp_cf_slim.rros/file_contexts b/apex/com.google.aosp_cf_slim.rros/file_contexts
new file mode 100644
index 0000000..cb7fd8d
--- /dev/null
+++ b/apex/com.google.aosp_cf_slim.rros/file_contexts
@@ -0,0 +1,2 @@
+(/.*)?		u:object_r:vendor_file:s0
+/overlay(/.*)?	u:object_r:vendor_overlay_file:s0
diff --git a/apex/com.google.cf.bt/Android.bp b/apex/com.google.cf.bt/Android.bp
new file mode 100644
index 0000000..438db02
--- /dev/null
+++ b/apex/com.google.cf.bt/Android.bp
@@ -0,0 +1,45 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+    name: "com.google.cf.bt.rc",
+    src: "com.google.cf.bt.rc",
+    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@1.1-service.btlinux",
+        "bt_vhci_forwarder",
+    ],
+    prebuilts: [
+        "android.hardware.bluetooth_le.prebuilt.xml",
+        "android.hardware.bluetooth.prebuilt.xml",
+        "com.google.cf.bt.rc",
+    ],
+    init_rc: ["com.google.cf.bt.trig.rc"],
+    vintf_fragments: [":manifest_android.hardware.bluetooth@1.1-service.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
new file mode 100644
index 0000000..a5f2ae7
--- /dev/null
+++ b/apex/com.google.cf.bt/com.google.cf.bt.rc
@@ -0,0 +1,9 @@
+service bt_vhci_forwarder /apex/com.google.cf.bt/bin/bt_vhci_forwarder -virtio_console_dev=${vendor.ser.bt-uart}
+    user bluetooth
+    group bluetooth
+
+service btlinux-1.1 /apex/com.google.cf.bt/bin/hw/android.hardware.bluetooth@1.1-service.btlinux
+    class hal
+    user bluetooth
+    group bluetooth net_admin net_bt_admin
+    capabilities NET_ADMIN
diff --git a/apex/com.google.cf.bt/com.google.cf.bt.trig.rc b/apex/com.google.cf.bt/com.google.cf.bt.trig.rc
new file mode 100644
index 0000000..a082c18
--- /dev/null
+++ b/apex/com.google.cf.bt/com.google.cf.bt.trig.rc
@@ -0,0 +1,6 @@
+## Init files within the APEX do not support triggers (b/202731768)
+## By adding this as an init_rc parameter of the APEX the file will be installed
+## outside of the APEX and instead be installed under /vendor/etc/init.
+on post-fs-data
+    start bt_vhci_forwarder
+
diff --git a/apex/com.google.cf.bt/file_contexts b/apex/com.google.cf.bt/file_contexts
new file mode 100644
index 0000000..b148753
--- /dev/null
+++ b/apex/com.google.cf.bt/file_contexts
@@ -0,0 +1,4 @@
+(/.*)?                                                 u:object_r:vendor_file:s0
+/bin/hw/android.hardware.bluetooth@1.1-service.btlinux 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
\ No newline at end of file
diff --git a/apex/com.google.cf.bt/manifest.json b/apex/com.google.cf.bt/manifest.json
new file mode 100644
index 0000000..0436efe
--- /dev/null
+++ b/apex/com.google.cf.bt/manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.google.cf.bt",
+  "version": 1
+}
diff --git a/apex/com.google.cf.input.config/Android.bp b/apex/com.google.cf.input.config/Android.bp
new file mode 100644
index 0000000..00edee9
--- /dev/null
+++ b/apex/com.google.cf.input.config/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex {
+    name: "com.google.cf.input.config",
+    // InputDevice expects input config files in /apex/com.android.input.config/etc
+    apex_name: "com.android.input.config",
+    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,
+    prebuilts: [
+        "Crosvm_Virtio_Multitouch_Touchscreen_0.idc",
+        "Crosvm_Virtio_Multitouch_Touchscreen_1.idc",
+        "Crosvm_Virtio_Multitouch_Touchscreen_2.idc",
+        "Crosvm_Virtio_Multitouch_Touchscreen_3.idc",
+    ],
+}
diff --git a/apex/com.google.cf.input.config/apex_manifest.json b/apex/com.google.cf.input.config/apex_manifest.json
new file mode 100644
index 0000000..dce7ad4
--- /dev/null
+++ b/apex/com.google.cf.input.config/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.input.config",
+  "version": 1
+}
diff --git a/apex/com.google.cf.input.config/file_contexts b/apex/com.google.cf.input.config/file_contexts
new file mode 100644
index 0000000..e982bd5
--- /dev/null
+++ b/apex/com.google.cf.input.config/file_contexts
@@ -0,0 +1,4 @@
+(/.*)?                          u:object_r:vendor_file:s0
+/etc/usr/keylayout(/.*)?\.kl    u:object_r:vendor_keylayout_file:s0
+/etc/usr/keychars(/.*)?\.kcm    u:object_r:vendor_keychars_file:s0
+/etc/usr/idc(/.*)?\.idc         u:object_r:vendor_idc_file:s0
diff --git a/apex/com.google.cf.rild/Android.bp b/apex/com.google.cf.rild/Android.bp
new file mode 100644
index 0000000..9e237b5
--- /dev/null
+++ b/apex/com.google.cf.rild/Android.bp
@@ -0,0 +1,59 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+    name: "com.google.cf.rild.rc",
+    src: "com.google.cf.rild.rc",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "ld.config.txt",
+    src: "ld.config.txt",
+    installable: false,
+}
+
+apex {
+    name: "com.google.cf.rild",
+    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,
+    binaries: [
+        "libcuttlefish-rild",
+    ],
+    native_shared_libs: [
+        "libcuttlefish-ril-2",
+    ],
+    prebuilts: [
+        "android.hardware.telephony.gsm.prebuilt.xml",
+        "android.hardware.telephony.ims.prebuilt.xml",
+        "com.google.cf.rild.rc",
+        "ld.config.txt",
+    ],
+    vintf_fragments: [":libril-modem-lib-manifests"],
+    overrides: [
+        "libril",
+        "libreference-ril",
+        "rild",
+    ],
+}
diff --git a/apex/com.google.cf.rild/apex_manifest.json b/apex/com.google.cf.rild/apex_manifest.json
new file mode 100644
index 0000000..c096789
--- /dev/null
+++ b/apex/com.google.cf.rild/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.google.cf.rild",
+  "version": 1
+}
diff --git a/apex/com.google.cf.rild/com.google.cf.rild.rc b/apex/com.google.cf.rild/com.google.cf.rild.rc
new file mode 100644
index 0000000..ff8b888
--- /dev/null
+++ b/apex/com.google.cf.rild/com.google.cf.rild.rc
@@ -0,0 +1,5 @@
+service vendor.ril-daemon /apex/com.google.cf.rild/bin/hw/libcuttlefish-rild
+    class main
+    user radio
+    group radio inet misc audio log readproc wakelock
+    capabilities BLOCK_SUSPEND NET_ADMIN NET_RAW
diff --git a/apex/com.google.cf.rild/file_contexts b/apex/com.google.cf.rild/file_contexts
new file mode 100644
index 0000000..fc0d328
--- /dev/null
+++ b/apex/com.google.cf.rild/file_contexts
@@ -0,0 +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
diff --git a/apex/com.google.cf.rild/ld.config.txt b/apex/com.google.cf.rild/ld.config.txt
new file mode 100644
index 0000000..c088357
--- /dev/null
+++ b/apex/com.google.cf.rild/ld.config.txt
@@ -0,0 +1,7 @@
+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/apex/com.google.cf.wifi/Android.bp b/apex/com.google.cf.wifi/Android.bp
new file mode 100644
index 0000000..3a57dd6
--- /dev/null
+++ b/apex/com.google.cf.wifi/Android.bp
@@ -0,0 +1,73 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+    name: "com.google.cf.wifi.rc",
+    src: "com.google.cf.wifi.rc",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "wpa_supplicant.conf.cf",
+    src: ":wpa_supplicant_template.conf",
+    filename: "wpa_supplicant.conf",
+    relative_install_path: "wifi",
+    installable: false,
+}
+
+apex_defaults {
+    name: "com.google.cf.wifi.defaults",
+    // Name expected by wpa_supplicant when it looks for config files.
+    apex_name: "com.android.wifi.hal",
+    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,
+    binaries: [
+        "rename_netiface",
+        "setup_wifi",
+        "wpa_supplicant_cf",
+    ],
+    prebuilts: [
+        "android.hardware.wifi.prebuilt.xml",
+        "com.google.cf.wifi.rc",
+        "wpa_supplicant.conf.cf",
+        "wpa_supplicant_overlay.conf.cf",
+    ],
+    // TODO(b/202992812): Use the vintf_fragment from the wpa_supplicant project.
+    vintf_fragments: ["com.google.cf.wifi.xml"],
+}
+
+apex {
+    name: "com.google.cf.wifi",
+    defaults: ["com.google.cf.wifi.defaults"],
+    prebuilts: [
+        "android.hardware.wifi.passpoint.prebuilt.xml",
+    ],
+    multi_install_skip_symbol_files: true,
+}
+
+apex {
+    name: "com.google.cf.wifi.no-passpoint",
+    defaults: ["com.google.cf.wifi.defaults"],
+    multi_install_skip_symbol_files: true,
+}
diff --git a/apex/com.google.cf.wifi/apex_manifest.json b/apex/com.google.cf.wifi/apex_manifest.json
new file mode 100644
index 0000000..ffd1a2c
--- /dev/null
+++ b/apex/com.google.cf.wifi/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.wifi.hal",
+  "version": 1
+}
diff --git a/apex/com.google.cf.wifi/com.google.cf.wifi.rc b/apex/com.google.cf.wifi/com.google.cf.wifi.rc
new file mode 100644
index 0000000..8551506
--- /dev/null
+++ b/apex/com.google.cf.wifi/com.google.cf.wifi.rc
@@ -0,0 +1,12 @@
+service rename_eth0 /apex/com.android.wifi.hal/bin/rename_netiface eth0 buried_eth0
+    oneshot
+
+service setup_wifi /apex/com.android.wifi.hal/bin/setup_wifi
+    oneshot
+
+service wpa_supplicant /apex/com.android.wifi.hal/bin/hw/wpa_supplicant_cf -g@android:wpa_wlan0
+    interface aidl android.hardware.wifi.supplicant.ISupplicant/default
+    socket wpa_wlan0 dgram 660 wifi wifi
+    group system wifi inet
+    disabled
+    oneshot
diff --git a/guest/hals/ril/reference-libril/android.hardware.radio.config@1.3.xml b/apex/com.google.cf.wifi/com.google.cf.wifi.xml
similarity index 62%
copy from guest/hals/ril/reference-libril/android.hardware.radio.config@1.3.xml
copy to apex/com.google.cf.wifi/com.google.cf.wifi.xml
index 97bf66d..772096c 100644
--- a/guest/hals/ril/reference-libril/android.hardware.radio.config@1.3.xml
+++ b/apex/com.google.cf.wifi/com.google.cf.wifi.xml
@@ -1,10 +1,10 @@
 <manifest version="1.0" type="device">
     <hal format="hidl">
-        <name>android.hardware.radio.config</name>
+        <name>android.hardware.wifi.supplicant</name>
         <transport>hwbinder</transport>
-        <version>1.3</version>
+        <version>1.4</version>
         <interface>
-            <name>IRadioConfig</name>
+            <name>ISupplicant</name>
             <instance>default</instance>
         </interface>
     </hal>
diff --git a/apex/com.google.cf.wifi/file_contexts b/apex/com.google.cf.wifi/file_contexts
new file mode 100644
index 0000000..b11e272
--- /dev/null
+++ b/apex/com.google.cf.wifi/file_contexts
@@ -0,0 +1,5 @@
+(/.*)?                       u:object_r:vendor_file:s0
+/bin/rename_netiface         u:object_r:rename_netiface_exec:s0
+/bin/setup_wifi              u:object_r:setup_wifi_exec:s0
+/bin/hw/wpa_supplicant_cf    u:object_r:hal_wifi_supplicant_default_exec:s0
+/etc/permissions(/.*)?       u:object_r:vendor_configs_file:s0
diff --git a/apex/com.google.cf.wifi_hwsim/Android.bp b/apex/com.google.cf.wifi_hwsim/Android.bp
new file mode 100644
index 0000000..6b8b9ce
--- /dev/null
+++ b/apex/com.google.cf.wifi_hwsim/Android.bp
@@ -0,0 +1,81 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+soong_namespace {
+    imports: [
+        "device/generic/goldfish",
+    ],
+}
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+    name: "com.google.cf.wifi_hwsim.rc",
+    src: "com.google.cf.wifi_hwsim.rc",
+    installable: false,
+}
+
+cc_binary {
+    name: "android.hardware.wifi@1.0-service_cf",
+    defaults: ["android.hardware.wifi@1.0-service_default"],
+    shared_libs: ["libwifi-hal_cf"],
+    static_libs: ["android.hardware.wifi@1.0-service-lib_cf"],
+}
+
+cc_library_static {
+    name: "android.hardware.wifi@1.0-service-lib_cf",
+    defaults: ["android.hardware.wifi@1.0-service-lib_defaults"],
+    shared_libs: ["libwifi-hal_cf"],
+}
+
+cc_library_shared {
+    name: "libwifi-hal_cf",
+    defaults: ["libwifi-hal_defaults"],
+    whole_static_libs: ["libwifi-hal-emu"],
+}
+
+apex {
+    name: "com.google.cf.wifi_hwsim",
+    // Name expected by wpa_supplicant when it looks for config files.
+    apex_name: "com.android.wifi.hal",
+    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,
+    binaries: [
+        "mac80211_create_radios",
+        "rename_netiface",
+        "wpa_supplicant_cf",
+        "hostapd_cf",
+        "android.hardware.wifi@1.0-service_cf",
+    ],
+    sh_binaries: ["init.wifi.sh_apex"],
+    prebuilts: [
+        "android.hardware.wifi.direct.prebuilt.xml",
+        "android.hardware.wifi.passpoint.prebuilt.xml",
+        "android.hardware.wifi.prebuilt.xml",
+        "com.google.cf.wifi_hwsim.rc",
+        "p2p_supplicant.conf.cf",
+        "wpa_supplicant.conf.cf",
+        "wpa_supplicant_overlay.conf.cf",
+    ],
+    // TODO(b/202992812): Use the vintf_fragment from the wpa_supplicant project.
+    vintf_fragments: ["com.google.cf.wifi_hwsim.xml"],
+}
diff --git a/apex/com.google.cf.wifi_hwsim/apex_manifest.json b/apex/com.google.cf.wifi_hwsim/apex_manifest.json
new file mode 100644
index 0000000..ffd1a2c
--- /dev/null
+++ b/apex/com.google.cf.wifi_hwsim/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.wifi.hal",
+  "version": 1
+}
diff --git a/apex/com.google.cf.wifi_hwsim/com.google.cf.wifi_hwsim.rc b/apex/com.google.cf.wifi_hwsim/com.google.cf.wifi_hwsim.rc
new file mode 100644
index 0000000..3dd0440
--- /dev/null
+++ b/apex/com.google.cf.wifi_hwsim/com.google.cf.wifi_hwsim.rc
@@ -0,0 +1,41 @@
+
+service rename_eth0 /apex/com.android.wifi.hal/bin/rename_netiface eth0 buried_eth0
+    oneshot
+
+service init_wifi_sh /apex/com.android.wifi.hal/bin/init.wifi.sh
+    class late_start
+    user root
+    group root wakelock wifi
+    oneshot
+    disabled    # Started on post-fs-data
+
+service wpa_supplicant /apex/com.android.wifi.hal/bin/hw/wpa_supplicant_cf \
+        -O/data/vendor/wifi/wpa/sockets -puse_p2p_group_interface=1p2p_device=1 \
+        -m/apex/com.android.wifi.hal/etc/wifi/p2p_supplicant.conf \
+        -g@android:wpa_wlan0 -dd
+    interface aidl android.hardware.wifi.supplicant.ISupplicant/default
+    socket wpa_wlan0 dgram 660 wifi wifi
+    group system wifi inet
+    disabled
+    oneshot
+
+service hostapd /apex/com.android.wifi.hal/bin/hw/hostapd_cf
+    interface aidl android.hardware.wifi.hostapd.IHostapd/default
+    class main
+    capabilities NET_ADMIN NET_RAW
+    user wifi
+    group wifi net_raw net_admin
+    disabled
+    oneshot
+
+service vendor.wifi_hal_legacy /apex/com.android.wifi.hal/bin/hw/android.hardware.wifi@1.0-service_cf
+    interface android.hardware.wifi@1.0::IWifi default
+    interface android.hardware.wifi@1.1::IWifi default
+    interface android.hardware.wifi@1.2::IWifi default
+    interface android.hardware.wifi@1.3::IWifi default
+    interface android.hardware.wifi@1.4::IWifi default
+    interface android.hardware.wifi@1.5::IWifi default
+    class hal
+    capabilities NET_ADMIN NET_RAW SYS_MODULE
+    user wifi
+    group wifi gps
diff --git a/apex/com.google.cf.wifi_hwsim/com.google.cf.wifi_hwsim.xml b/apex/com.google.cf.wifi_hwsim/com.google.cf.wifi_hwsim.xml
new file mode 100644
index 0000000..05eb2c0
--- /dev/null
+++ b/apex/com.google.cf.wifi_hwsim/com.google.cf.wifi_hwsim.xml
@@ -0,0 +1,19 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.wifi.supplicant</name>
+        <fqname>ISupplicant/default</fqname>
+    </hal>
+    <hal format="aidl">
+        <name>android.hardware.wifi.hostapd</name>
+        <fqname>IHostapd/default</fqname>
+    </hal>
+    <hal format="hidl">
+        <name>android.hardware.wifi</name>
+        <transport>hwbinder</transport>
+        <version>1.6</version>
+        <interface>
+            <name>IWifi</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+</manifest>
diff --git a/apex/com.google.cf.wifi_hwsim/file_contexts b/apex/com.google.cf.wifi_hwsim/file_contexts
new file mode 100644
index 0000000..083cfed
--- /dev/null
+++ b/apex/com.google.cf.wifi_hwsim/file_contexts
@@ -0,0 +1,8 @@
+(/.*)?                       u:object_r:vendor_file:s0
+/bin/rename_netiface         u:object_r:rename_netiface_exec:s0
+/bin/init\.wifi\.sh            u:object_r:init_wifi_sh_exec:s0
+/bin/hw/wpa_supplicant_cf    u:object_r:hal_wifi_supplicant_default_exec:s0
+/bin/hw/hostapd_cf           u:object_r:hal_wifi_hostapd_default_exec:s0
+/bin/mac80211_create_radios  u:object_r:mac80211_create_radios_exec:s0
+/etc/permissions(/.*)?       u:object_r:vendor_configs_file:s0
+/bin/hw/android\.hardware\.wifi@1\.0-service_cf      u:object_r:hal_wifi_default_exec:s0
diff --git a/host/commands/tapsetiff/Android.bp b/apex/keys/Android.bp
similarity index 67%
copy from host/commands/tapsetiff/Android.bp
copy to apex/keys/Android.bp
index 1d7dedb..85184bc 100644
--- a/host/commands/tapsetiff/Android.bp
+++ b/apex/keys/Android.bp
@@ -1,5 +1,4 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,7 +16,13 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-sh_binary_host {
-    name: "tapsetiff",
-    src: "tapsetiff.py",
+apex_key {
+    name: "com.google.cf.apex.key",
+    public_key: "com.google.cf.apex.avbpubkey",
+    private_key: "com.google.cf.apex.pem",
+}
+
+android_app_certificate {
+    name: "com.google.cf.apex.certificate",
+    certificate: "com.google.cf.apex",
 }
diff --git a/apex/keys/com.google.cf.apex.avbpubkey b/apex/keys/com.google.cf.apex.avbpubkey
new file mode 100644
index 0000000..1fc39e1
--- /dev/null
+++ b/apex/keys/com.google.cf.apex.avbpubkey
Binary files differ
diff --git a/apex/keys/com.google.cf.apex.pem b/apex/keys/com.google.cf.apex.pem
new file mode 100644
index 0000000..59de332
--- /dev/null
+++ b/apex/keys/com.google.cf.apex.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAvAG5AIYljraeMBaaVr+UYPFZ/GEDn1+irto2iWtPJxaSIQwU
+t0GMU5DtRMds3XK0PaQOYusoQXQnf3nnb6Ake8NtifsI+pw2HRnx+bwlbvoUBlA4
+g0hpMQThdryevg1hT8bdgQDRm8LI/xIGZ/HH5jKgNHcEmxJuVTmBfSjokaIJDb/M
+ZrTr9agFALIHh6JAdRDXUE4Myr3a8pzeAN71Y5Ll99c1Ni3uc5Fx/83q+vtlL5qE
+zDCsBj1q7gvkdiYFtEGsm+tJ5+sG5/aUKaOq05u0vZRsLInlacub/6Vy5G+uBAXc
+Lzx14rXvzcKkcvgbkpmedPdbv3JR4Pg0oGtgPXG9Qh3bU1XsjaYUzp9c1jhQjs+q
+qBGKOKex1sVF/6/Wn6EjYGKawxHPQtFEp5DcIv7nrQDPFWy6GA4CmO/vaWfQcmIA
+9ikCZGuqDhKJpmJ4WO145u1qgnNHsBLwHMtxHoz7nq0bJUcdCxbrNutUkn4lFuFs
+JUUZdFxKeJkYcT+0+Ml1P6girGXaiLE8JyYBTps+Y91mGnMuNDezseZBEYpX1rP+
+Qx5pdbMFt8anp5bOar76dyGI/wq12kgfnpyweItXRULiIS9EsPxT6birh1BzXtpq
+jnsdEpC5uXidise1rvdNkrgbpCZC9ZDpRzBORWz1OW8+EhmoGhtvBPD6wLsCAwEA
+AQKCAgAW5SrPaoa2W3zmJEqFV+1M5Pdtaa8UQIRCQOa1U3EfNHt1NNBtBLl/D74l
+Sxfx298hRpJN749GcUvCFWleyaTHwaPcUsrkIhPg9WDnZcc1PZUks64+JppQ0uRW
+HmBCisSX/4LIC/56tnzduyc2j1YlrXKfEQNpkxQGousm/81att3dY8cTluLJVr3N
+OOD73oF1ACkIaYjbQ8WfGAVdG8nMZ35D8VxUjcFlJ4g3e68rA2RuKKYVa7P3SpF0
+DdSzoqu9KOZJUpz8dj2wD/I5I+pQvLyE/ccyoVRjztzfhBl6wjLx4HjQ887zXe6n
+IxX9vkM1Vina2qi8psJb4D4gbxMYEvXnOhequ1TSvd03f91gR5D9KFxspGF8NHRA
+gkzKT7QGqgOS31IANC0wIdH5tVrLSVxhtq5BKxdxywTZz4wA/Gy32o7D7phX9PhH
+MZDSplwi92ft1UoU1VLM5eCRSDJf9LM3HVXTpzlmMwCalR+AVnCxNa6rKvDfAbmn
+BVOoh8vNkEfRBwmI6ctXaC7VVA+E4JdWMKk7BbKqU4yUVHrdSCyjwxomAh3G539w
+0YnS7mR8kw/D9ppkQ5bm6ndtu8ABd+VfBPzlvc3kgLjlco7zENNqMOhgrm5lKZsS
+s5WCrZ4feeeC4FZAUXf17SYas6WfB9Hr64Fs81vZmwfNpmgMGQKCAQEA6CsiJCh4
+FAbuLZAAJtJ3z/4uoJTLqfHPOHZFww2PJ7f5CwYIdOxBQ4/PBjz8wreajur3eYai
+T9uhKbG2+elO48ReMREytcpGox9t36wbxrrE185XpI4lQITvEyOv9fsV0af4Vk7W
+KljV5IMMauYwwouUwYV4VPQHVOUMKNy0u/68A6NTjIAXU41pQHzo+bCtyCjpw8ol
+7dQxHzOFbngoSv0s+FT1a+myDJPSAj0BjDTlsaD+Gplcvag4qMOA28IBjcFiv4l5
+e75RF0CIWzRvOJsR3G7WkhxTH1jn4HYOjoYRAZ5YTHq7QM5/xZdulA0sT2zWzY6V
+YlOICG9jZTpI/wKCAQEAz04eDQtDme2jvzdMft0OebsEXuRudCpGiZ3hkPthBelk
+eeFEt9yl2oU9fOOQhjY369gLK3tkN+O6hpGV9bWC2Iua1o7gsSZDp8CKZFOK9JD7
+aqvmND0R3tRCqVaaraIqgWx7kFg5x7Lfgi/KFuXZy7umCbVQqtxDBaSW+Nso2m9m
+0mnr41r9Z52gqJV+z1zyjRY4wswtAmUDcRDnwkJsX6hKFxTRvxRvy1JK1whBgflM
+3oHjUNQFSeeKa2rdZzDSy7ZRCTwl5gZI2CsURX3XmFvYOXdioDACUlpcjzK8Trcq
+/2+gz/2nLmMgVvp5tHYhYHovEks8BoAyAofFyQbsRQKCAQBSH5/OBnqjKuhpOXy0
+PtKewhygNMHt9VkFceCvZEZ1GECBw7qOEVvsmBv06vHFtsh3MWoklJkpglj5tKEy
+uXJsYvOmi5zSbSCbZuyop+qTW1FxvM2HqbhHoD4pGQCPFCfdp3rSnMRo6k+Oq0Rj
+M9Wfm1wdMCcmdcN6JiMs+RT9QtgiuU0+b7jQlz7ZztViLTrriH1YAlN0UxClJsZW
+Ey69h9y2YucFKv8OL+OjYwz/GV7+fCImKoWBmNWh7LXSBkgianuRoQFV4jYw8WTK
+TjvhXAjvXk2MFXTZq8spvNjdVVMCrY4yT1+ZRvIvZKd6u0YnOiqpP3xb8Yw235/b
+GMjlAoIBAQCav7ubDR+Hlme34/XMdgPKRxr6Ixd4y94f+KVbbut8WD9S5CBCCAoe
+13uQ8Ob/6RVRjtK3wMKNHggtUBxbcQWd1IjfRYTheKjkXsxwHBUMf/XOKUgNEtF2
+P4kLk8SffQCx4GNU2yc2tYY3TqlS8n3koc1OTfVLtmSpn7W7Sw5yENr2k28tJs0n
+PfmiHwaskLvXKhFxCK1IrlMlYfM/hgoUVjIIjNgOBZl2c5W+c0FDXvBM4TTpL3xL
+MPaZPQrNbxrMSuqvNCEuVt6lz3KwdUItT9JXA5Gx9mSlSSLzGnKLaBxG1fN7j+Pu
+srx/cTbMyaoctNjSlSrXx3aNgQDaEbrpAoIBAQCxwuK5O9dVHIL8KDh20GrUSVrI
+OapyM7xEjiOJbqEhRbfww3ZHJmw6KYahOnJ5M4gT+23olqRK0pkV80O1H0hfBOF6
+WUlYxW/fBB2egSM568mZ0lXZAMDRxF19XpxzbPUlzTVoA9sZm9Djq6uqWCjYL0Rk
+0aj3DlMMPNMf3EBZJr2pI/GT1WssqKpvTv5gYXvsdoqvpGucW6z/MvilKgbU+RvQ
+V6zDyNdAepw65MCrH+doNroe9NEKS6Fg7RTgMKBWaHTaHJuayj+5/IvkHJ7ULTpW
+NoKv6EkvL2FuVsVa4fY+3KMhJamr4IkhCHhKeNDrAxEyHZ1QjAjhH9W0QDP0
+-----END RSA PRIVATE KEY-----
diff --git a/apex/keys/com.google.cf.apex.pk8 b/apex/keys/com.google.cf.apex.pk8
new file mode 100644
index 0000000..424387a
--- /dev/null
+++ b/apex/keys/com.google.cf.apex.pk8
Binary files differ
diff --git a/apex/keys/com.google.cf.apex.x509.pem b/apex/keys/com.google.cf.apex.x509.pem
new file mode 100644
index 0000000..db073ba
--- /dev/null
+++ b/apex/keys/com.google.cf.apex.x509.pem
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFyTCCA7ECFEDbudaUmJ6QxCAviuzTSQwh55rKMA0GCSqGSIb3DQEBCwUAMIGf
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEbMBkGA1UEAwwSY29t
+Lmdvb2dsZS5jZi5yaWxkMCAXDTIxMTAwMTE3MjQyMVoYDzQ3NTkwODI4MTcyNDIx
+WjCBnzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
+DU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB0FuZHJvaWQxEDAOBgNVBAsMB0FuZHJv
+aWQxIjAgBgkqhkiG9w0BCQEWE2FuZHJvaWRAYW5kcm9pZC5jb20xGzAZBgNVBAMM
+EmNvbS5nb29nbGUuY2YucmlsZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
+ggIBAKGd7f1fKnpky183oJHp5xAam+LeyMcCMg+VPaRMg5T2Uw4kPlJrwxDx0n6S
+WNtqVvcZMRfUREw6BW6mWhOae1rarVSJDxo+pzcju66zVYMf38FqzrBLPSzkKOvK
+pC3WiTs193+lrWsL6a3XtNx3gN3J4cc4f+gEASiE93mbN/pHirIgpLwmZMpaHAXd
+KZBk8iXPrwusdCAi0F6v9AxP0JUhYBmqr+/q5mFHevRq7UBykBjRqNTylmDPjjxu
+fRDkOmnzs8/htToT03NXrDhxod5GIZ/+NsVaTECZ4PK/+BBnMBg/FRE4kPpolMkp
+Nt72pkGDL0whz7RrUxhHfFDVKngmmspBpBIF4bVXQz5yuYqDaEiZv/7x8yjsbkkF
++eraEi9HBy6klhlbYV2XDA/qvV8twocJS/5Qql9bGmH/fHX3SbXTI/RD4kiopxXL
+rCLiV5ABjaFEUp5ub2IHP4W/lXpWqc1GFZfg59wL6NO/dJGO7UsHW/M2euJ4RU4i
+w3kl0J2TmjM1mCbWPsYkyCtW+QwkSz1RpB2cI2v+oYc5vmIceZuu3OY80mzvhqEK
+45Nu1qGoExmrdgVqF6h8LHMSRAMAlx+13Glt2QNEdaEsc9E1oPxXXK0oOLEI/F0B
+9pGGMgVZFPSGtQ8nWNAxy2v+WwtzNQO9p5ra6atYHkbwfhcdAgMBAAEwDQYJKoZI
+hvcNAQELBQADggIBAFpj7pYQHw75MK1Np27IVwhegZrJNb2g8lpYuD/Qzx9bPBTg
+BJ6hSnjPQwgFeAA1mtyh9CzJPzBRrGXJz/zp/VojFs1G4vqm3if+MC+ONKzxtlf1
+q4IPd/DsFce9Ak2NDyAp6IFDZhtEQxv48CclYy2JsIVqlPh2aWN2/VbHQ2e/D8ZK
+MIrTiSPjogDSPcK4MLbavSCQlxZIr+bfNhqm78oWhrpTrJu0+6rXNDJ82LpobmEV
+lp2UmFPVc1sm+Al317Wo5durfF99YSDOF2u9lKO81X4b5Jjc0a6n1q/hHlrMSMuq
++5wn+KVtKRAZf5dU7YGRTPLYKq1Yf0Zygd5lXxG2xhtwaHnwTY3g5cgKLHGzJ9hk
+FbLxVR86ZXiFnZyEncfC8PjUBX6fK2hy1bi1GZSj6jRwsUTx6ay5m28Y4EA+ix89
+hcqrXNR2dumpZTtjirjIrr2hbQB/Mf+LGSmUSZzLaL8vF6Owaxrqg7r3L4NnY6o7
+P45QehDTmm6DQg/8y47KPeKuHIf3JjW2WavcyUF0d/FtjXtPGB+t9Uskfhmv+qIm
+/3RAGckgLIi1+38bwVeHI+EnljZ2PLaXarEFjecI9HmA7A40MQWxaQns91nIjKDE
+8jpIVI+lTRpCPNJAfEm+txvE6PnlAXqDCIhIfl/sX/sIJguqoI/NV+1xJYQg
+-----END CERTIFICATE-----
diff --git a/build/Android.bp b/build/Android.bp
index d516313..dbae9bf 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -23,6 +23,7 @@
     module_type: "cvd_host_package",
     config_namespace: "cvd",
     value_variables: [
+        "grub_config",
         "launch_configs",
         "custom_action_config",
         "custom_action_servers",
@@ -33,21 +34,28 @@
     "android.hardware.automotive.vehicle@2.0-virtualization-grpc-server",
     "adb",
     "adb_connector",
-    "adbshell",
     "allocd",
     "allocd_client",
     "assemble_cvd",
+    "avbtool",
     "bt_connector",
     "common_crosvm",
     "config_server",
     "console_forwarder",
     "crosvm",
+    "cvd",
+    "cvd_internal_host_bugreport",
+    "cvd_internal_start",
+    "cvd_internal_status",
+    "cvd_internal_stop",
     "cvd_host_bugreport",
     "cvd_status",
+    "cvd_test_gce_driver",
     "extract-ikconfig",
     "extract-vmlinux",
     "fsck.f2fs",
     "gnss_grpc_proxy",
+    "health",
     "kernel_log_monitor",
     "launch_cvd",
     "libgrpc++",
@@ -61,27 +69,35 @@
     "metrics",
     "mkbootfs",
     "mkbootimg",
-    "mkenvimage",
+    "mkenvimage_slim",
     "modem_simulator",
     "ms-tpm-20-ref",
+    "mcopy",
+    "mmd",
+    "mtools",
     "newfs_msdos",
     "powerwash_cvd",
     "restart_cvd",
     "root-canal",
-    // TODO(b/186487510): remove libchrome and libbacktrace when ASan-related dependency issue is resolved.
-    "libchrome",
-    "libbacktrace",
     "run_cvd",
     "secure_env",
+    "cvd_send_sms",
     "socket_vsock_proxy",
     "stop_cvd",
-    "tapsetiff",
     "tombstone_receiver",
     "toybox",
     "unpack_bootimg",
-    "vnc_server",
     "webRTC",
     "webrtc_operator",
+    "operator_proxy",
+    "wmediumd",
+    "wmediumd_control",
+    "wmediumd_gen_config",
+]
+
+cvd_openwrt_images = [
+    "kernel_for_openwrt",
+    "openwrt_rootfs",
 ]
 
 cvd_bluetooth_config_files = [
@@ -97,14 +113,18 @@
 cvd_host_webrtc_assets = [
     "webrtc_adb.js",
     "webrtc_app.js",
+    "webrtc_index.js",
     "webrtc_controls.js",
     "webrtc_cf.js",
+    "webrtc_server_connector.js",
     "webrtc_index.html",
+    "webrtc_client.html",
     "webrtc_rootcanal.js",
     "webrtc_server.crt",
     "webrtc_server.key",
     "webrtc_server.p12",
     "webrtc_style.css",
+    "webrtc_index.css",
     "webrtc_controls.css",
     "webrtc_trusted.pem",
 ]
@@ -118,11 +138,15 @@
 cvd_host_seccomp_policy_x86_64 = [
     "9p_device.policy_x86_64",
     "balloon_device.policy_x86_64",
+    "battery.policy_x86_64",
     "block_device.policy_x86_64",
     "cras_audio_device.policy_x86_64",
+    "cras_snd_device.policy_x86_64",
     "fs_device.policy_x86_64",
     "gpu_device.policy_x86_64",
+    "gpu_render_server.policy_x86_64",
     "input_device.policy_x86_64",
+    "iommu_device.policy_x86_64",
     "net_device.policy_x86_64",
     "null_audio_device.policy_x86_64",
     "pmem_device.policy_x86_64",
@@ -138,13 +162,16 @@
     "xhci.policy_x86_64",
 ]
 
-cvd_host_seccomp_policy_arm64 = [
+cvd_host_seccomp_policy_aarch64 = [
     "9p_device.policy_aarch64",
     "balloon_device.policy_aarch64",
+    "battery.policy_aarch64",
     "block_device.policy_aarch64",
     "cras_audio_device.policy_aarch64",
+    "cras_snd_device.policy_aarch64",
     "fs_device.policy_aarch64",
     "gpu_device.policy_aarch64",
+    "gpu_render_server.policy_aarch64",
     "input_device.policy_aarch64",
     "net_device.policy_aarch64",
     "null_audio_device.policy_aarch64",
@@ -159,6 +186,23 @@
     "xhci.policy_aarch64",
 ]
 
+cvd_host_qemu_bootloader = [
+    "bootloader_qemu_x86_64",
+    "bootloader_qemu_aarch64",
+    "bootloader_qemu_arm",
+]
+
+prebuilt_etc_host {
+    name: "cvd_avb_testkey",
+    filename: "cvd_avb_testkey.pem",
+    src: ":avb_testkey_rsa4096",
+}
+
+cvd_host_avb_testkey = [
+    "cvd_avb_pubkey",
+    "cvd_avb_testkey",
+]
+
 cvd_host_package_customization {
     name: "cvd-host_package",
     deps: cvd_host_tools +
@@ -166,8 +210,11 @@
     multilib: {
         common: {
             deps: cvd_host_webrtc_assets +
+                cvd_host_avb_testkey +
                 cvd_host_model_simulator_files +
-                cvd_bluetooth_config_files,
+                cvd_host_qemu_bootloader +
+                cvd_bluetooth_config_files +
+                cvd_openwrt_images,
         },
     },
 
@@ -182,7 +229,7 @@
         arm64: {
             multilib: {
                 common: {
-                    deps: cvd_host_seccomp_policy_arm64,
+                    deps: cvd_host_seccomp_policy_aarch64,
                 },
             },
         },
diff --git a/build/README.md b/build/README.md
index dc66c0a..cfba9c5 100644
--- a/build/README.md
+++ b/build/README.md
@@ -1,25 +1,26 @@
 ## Custom Actions
 
-To add custom actions to the WebRTC control panel, create a custom action config
-JSON file in your virtual device product makefile directory, create a
-`prebuilt_etc_host` module for the JSON file with `sub_dir`
-`cvd_custom_action_config`, then set the build variable
-`SOONG_CONFIG_cvd_custom_action_config` to the name of that module. For example:
+To add custom actions to the WebRTC control panel:
 
-```
-Android.bp:
-  prebuilt_etc_host {
-      name: "my_custom_action_config.json",
-      src: "my_custom_action_config.json",
-      // The sub_dir must always equal the following value:
-      sub_dir: "cvd_custom_action_config",
-  }
+*   Create a custom action config JSON file in your virtual device product
+    makefile directory.
+*   Create a `prebuilt_etc_host` module for the JSON file with `sub_dir`
+    `cvd_custom_action_config`
+*   Set the Soong config variable `custom_action_config` in the `cvd` namespace
+    to the name of that module. For example:
 
-my_virtual_device.mk:
-  SOONG_CONFIG_NAMESPACES += cvd
-  SOONG_CONFIG_cvd += custom_action_config
-  SOONG_CONFIG_cvd_custom_action_config := my_custom_action_config.json
-```
+    ```
+    Android.bp:
+      prebuilt_etc_host {
+          name: "my_custom_action_config.json",
+          src: "my_custom_action_config.json",
+          // The sub_dir must always equal the following value:
+          sub_dir: "cvd_custom_action_config",
+      }
+
+    my_virtual_device.mk:
+      $(call soong_config_set, cvd, custom_action_config, my_custom_action_config.json)
+    ```
 
 TODO(b/171709037): Add documentation to source.android.com
 
diff --git a/build/cvd-host-package.go b/build/cvd-host-package.go
index b025386..5ff885e 100644
--- a/build/cvd-host-package.go
+++ b/build/cvd-host-package.go
@@ -56,6 +56,12 @@
 		{Mutator: "arch", Variation: android.Common.String()},
 	}
 	for _, dep := range strings.Split(
+		ctx.Config().VendorConfig("cvd").String("grub_config"), " ") {
+		if ctx.OtherModuleExists(dep) {
+			ctx.AddVariationDependencies(variations, cvdHostPackageDependencyTag, dep)
+		}
+	}
+	for _, dep := range strings.Split(
 		ctx.Config().VendorConfig("cvd").String("launch_configs"), " ") {
 		if ctx.OtherModuleExists(dep) {
 			ctx.AddVariationDependencies(variations, cvdHostPackageDependencyTag, dep)
@@ -81,7 +87,7 @@
 
 func (c *cvdHostPackage) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	zipFile := android.PathForModuleOut(ctx, "package.zip")
-	c.CopyDepsToZip(ctx, zipFile)
+	c.CopyDepsToZip(ctx, c.GatherPackagingSpecs(ctx), zipFile)
 
 	// Dir where to extract the zip file and construct the final tar.gz from
 	packageDir := android.PathForModuleOut(ctx, ".temp")
diff --git a/common/frontend/socket_vsock_proxy/Android.bp b/common/frontend/socket_vsock_proxy/Android.bp
index 9335408..427d9c2 100644
--- a/common/frontend/socket_vsock_proxy/Android.bp
+++ b/common/frontend/socket_vsock_proxy/Android.bp
@@ -23,6 +23,7 @@
         "main.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
@@ -32,6 +33,7 @@
     ],
     static_libs: [
         "libgflags",
+        "libcuttlefish_utils",
     ],
     target: {
         host: {
diff --git a/common/frontend/socket_vsock_proxy/main.cpp b/common/frontend/socket_vsock_proxy/main.cpp
index cf68821..5d08790 100644
--- a/common/frontend/socket_vsock_proxy/main.cpp
+++ b/common/frontend/socket_vsock_proxy/main.cpp
@@ -15,19 +15,17 @@
  */
 
 #include <set>
-#include <thread>
 #include <android-base/logging.h>
 #include <gflags/gflags.h>
 
 #include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/socket2socket_proxy.h"
 #include "host/commands/kernel_log_monitor/utils.h"
 
 #ifdef CUTTLEFISH_HOST
 #include "host/libs/config/logging.h"
 #endif // CUTTLEFISH_HOST
 
-constexpr std::size_t kMaxPacketSize = 8192;
-
 DEFINE_string(server, "",
               "The type of server to host, `vsock` or `tcp`. When hosting a server "
               "of one type, the proxy will take inbound connections of this type and "
@@ -46,109 +44,6 @@
     "server and the corresponding port flag will be ignored");
 
 namespace {
-// Sends packets, Shutdown(SHUT_WR) on destruction
-class SocketSender {
- public:
-  explicit SocketSender(cuttlefish::SharedFD socket) : socket_{socket} {}
-
-  SocketSender(SocketSender&&) = default;
-  SocketSender& operator=(SocketSender&&) = default;
-
-  SocketSender(const SocketSender&&) = delete;
-  SocketSender& operator=(const SocketSender&) = delete;
-
-  ~SocketSender() {
-    if (socket_.operator->()) {  // check that socket_ was not moved-from
-      socket_->Shutdown(SHUT_WR);
-    }
-  }
-
-  ssize_t SendAll(const char* packet, ssize_t length) {
-    ssize_t written{};
-    while (written < length) {
-      if (!socket_->IsOpen()) {
-        return -1;
-      }
-      auto just_written =
-          socket_->Send(packet + written,
-                        length - written, MSG_NOSIGNAL);
-      if (just_written <= 0) {
-        LOG(WARNING) << "Couldn't write to client: "
-                     << strerror(socket_->GetErrno());
-        return just_written;
-      }
-      written += just_written;
-    }
-    return written;
-  }
-
- private:
-  cuttlefish::SharedFD socket_;
-};
-
-class SocketReceiver {
- public:
-  explicit SocketReceiver(cuttlefish::SharedFD socket) : socket_{socket} {}
-
-  SocketReceiver(SocketReceiver&&) = default;
-  SocketReceiver& operator=(SocketReceiver&&) = default;
-
-  SocketReceiver(const SocketReceiver&&) = delete;
-  SocketReceiver& operator=(const SocketReceiver&) = delete;
-
-  // return value will be 0 if Read returns 0 or error
-  ssize_t Recv(char* packet, ssize_t length) {
-    auto size = socket_->Read(packet, length);
-    if (size < 0) {
-      size = 0;
-    }
-
-    return size;
-  }
-
- private:
-  cuttlefish::SharedFD socket_;
-};
-
-void SocketToVsock(SocketReceiver socket_receiver,
-                   SocketSender vsock_sender) {
-  char packet[kMaxPacketSize] = {};
-
-  while (true) {
-    ssize_t length = socket_receiver.Recv(packet, kMaxPacketSize);
-    if (length == 0 || vsock_sender.SendAll(packet, length) < 0) {
-      break;
-    }
-  }
-  LOG(DEBUG) << "Socket to vsock exiting";
-}
-
-void VsockToSocket(SocketSender socket_sender,
-                   SocketReceiver vsock_receiver) {
-  char packet[kMaxPacketSize] = {};
-
-  while (true) {
-    ssize_t length = vsock_receiver.Recv(packet, kMaxPacketSize);
-    if (length == 0) {
-      break;
-    }
-    if (socket_sender.SendAll(packet, length) < 0) {
-      break;
-    }
-  }
-  LOG(DEBUG) << "Vsock to socket exiting";
-}
-
-// One thread for reading from shm and writing into a socket.
-// One thread for reading from a socket and writing into shm.
-void HandleConnection(cuttlefish::SharedFD vsock,
-                      cuttlefish::SharedFD socket) {
-  auto socket_to_vsock =
-      std::thread(SocketToVsock, SocketReceiver{socket}, SocketSender{vsock});
-  VsockToSocket(SocketSender{socket}, SocketReceiver{vsock});
-  socket_to_vsock.join();
-}
-
 void WaitForAdbdToBeStarted(int events_fd) {
   auto evt_shared_fd = cuttlefish::SharedFD::Dup(events_fd);
   close(events_fd);
@@ -170,7 +65,7 @@
 }
 
 // intented to run as cuttlefish host service
-[[noreturn]] void TcpServer() {
+void TcpServer() {
   LOG(DEBUG) << "starting TCP server on " << FLAGS_tcp_port
              << " for vsock port " << FLAGS_vsock_port;
   cuttlefish::SharedFD server;
@@ -184,10 +79,8 @@
   CHECK(server->IsOpen()) << "Could not start server on " << FLAGS_tcp_port;
   LOG(DEBUG) << "Accepting client connections";
   int last_failure_reason = 0;
-  while (true) {
-    auto client_socket = cuttlefish::SharedFD::Accept(*server);
-    CHECK(client_socket->IsOpen()) << "error creating client socket";
-    cuttlefish::SharedFD vsock_socket = cuttlefish::SharedFD::VsockClient(
+  cuttlefish::Proxy(server, [&last_failure_reason]() {
+    auto vsock_socket = cuttlefish::SharedFD::VsockClient(
         FLAGS_vsock_cid, FLAGS_vsock_port, SOCK_STREAM);
     if (vsock_socket->IsOpen()) {
       last_failure_reason = 0;
@@ -200,12 +93,9 @@
         LOG(ERROR) << "Unable to connect to vsock server: "
                    << vsock_socket->StrError();
       }
-      continue;
     }
-    auto thread = std::thread(HandleConnection, std::move(vsock_socket),
-                              std::move(client_socket));
-    thread.detach();
-  }
+    return vsock_socket;
+  });
 }
 
 cuttlefish::SharedFD OpenSocketConnection() {
@@ -232,7 +122,7 @@
 }
 
 // intended to run inside Android guest
-[[noreturn]] void VsockServer() {
+void VsockServer() {
   LOG(DEBUG) << "Starting vsock server on " << FLAGS_vsock_port;
   cuttlefish::SharedFD vsock;
   if (FLAGS_server_fd < 0) {
@@ -248,17 +138,12 @@
     close(FLAGS_server_fd);
   }
   CHECK(vsock->IsOpen()) << "Could not start server on " << FLAGS_vsock_port;
-  while (true) {
-    LOG(DEBUG) << "waiting for vsock connection";
-    auto vsock_client = cuttlefish::SharedFD::Accept(*vsock);
-    CHECK(vsock_client->IsOpen()) << "error creating vsock socket";
+  cuttlefish::Proxy(vsock, []() {
     LOG(DEBUG) << "vsock socket accepted";
     auto client = OpenSocketConnection();
     CHECK(client->IsOpen()) << "error connecting to guest client";
-    auto thread = std::thread(HandleConnection, std::move(vsock_client),
-                              std::move(client));
-    thread.detach();
-  }
+    return client;
+  });
 }
 
 }  // namespace
diff --git a/common/libs/concurrency/multiplexer.h b/common/libs/concurrency/multiplexer.h
index 065c1a2..478963f 100644
--- a/common/libs/concurrency/multiplexer.h
+++ b/common/libs/concurrency/multiplexer.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <condition_variable>
+#include <functional>
 #include <memory>
 #include <vector>
 
@@ -24,55 +25,64 @@
 #include "common/libs/concurrency/thread_safe_queue.h"
 
 namespace cuttlefish {
-namespace confui {
-template <typename T>
+template <typename T, typename Queue>
 class Multiplexer {
  public:
-  Multiplexer(int n_qs, int max_elements) : sem_items_{0}, next_{0} {
-    auto drop_new = [](typename ThreadSafeQueue<T>::QueueImpl* internal_q) {
-      internal_q->pop_front();
-    };
-    for (int i = 0; i < n_qs; i++) {
-      auto queue = std::make_unique<ThreadSafeQueue<T>>(max_elements, drop_new);
-      queues_.push_back(std::move(queue));
-    }
+  using QueuePtr = std::unique_ptr<Queue>;
+  using QueueSelector = std::function<int(void)>;
+
+  template <typename... Args>
+  static QueuePtr CreateQueue(Args&&... args) {
+    auto raw_ptr = new Queue(std::forward<Args>(args)...);
+    return QueuePtr(raw_ptr);
   }
 
-  int GetNewQueueId() {
-    CHECK(next_ < queues_.size())
-        << "can't get more queues than " << queues_.size();
-    return next_++;
+  Multiplexer() : sem_items_{0} {}
+
+  int RegisterQueue(QueuePtr&& queue) {
+    const int id_to_return = queues_.size();
+    queues_.push_back(std::move(queue));
+    return id_to_return;
   }
 
   void Push(const int idx, T&& t) {
     CheckIdx(idx);
-    queues_[idx]->Push(t);
+    queues_[idx]->Push(std::move(t));
     sem_items_.SemPost();
   }
 
-  T Pop() {
-    // the idx must have an item!
-    // no waiting in fn()!
-    sem_items_.SemWait();
-    for (auto& q : queues_) {
-      if (q->IsEmpty()) {
-        continue;
-      }
-      return q->Pop();
-    }
-    CHECK(false) << "Multiplexer.Pop() should be able to return an item";
-    // must not reach here
-    return T{};
+  T Pop(QueueSelector selector) {
+    SemWait();
+    int q_id = selector();
+    CheckIdx(q_id);  // check, if weird, will die there
+    QueuePtr& queue = queues_[q_id];
+    CHECK(queue) << "queue must not be null.";
+    return queue->Pop();
   }
 
+  T Pop() {
+    auto default_selector = [this]() -> int {
+      for (int i = 0; i < queues_.size(); i++) {
+        if (!queues_[i]->IsEmpty()) {
+          return i;
+        }
+      }
+      return -1;
+    };
+    return Pop(default_selector);
+  }
+
+  bool IsEmpty(const int idx) { return queues_[idx]->IsEmpty(); }
+
+  void SemWait() { sem_items_.SemWait(); }
+
  private:
   void CheckIdx(const int idx) {
     CHECK(idx >= 0 && idx < queues_.size()) << "queues_ array out of bound";
   }
   // total items across the queues
   Semaphore sem_items_;
-  std::vector<std::unique_ptr<ThreadSafeQueue<T>>> queues_;
-  int next_;
+  std::vector<QueuePtr> queues_;
+  QueuePtr null_ptr_;
 };
-}  // end of namespace confui
 }  // end of namespace cuttlefish
diff --git a/common/libs/concurrency/thread_safe_queue.h b/common/libs/concurrency/thread_safe_queue.h
index 9105299..ae16bef 100644
--- a/common/libs/concurrency/thread_safe_queue.h
+++ b/common/libs/concurrency/thread_safe_queue.h
@@ -20,6 +20,7 @@
 #include <deque>
 #include <iterator>
 #include <mutex>
+#include <type_traits>
 #include <utility>
 
 namespace cuttlefish {
@@ -34,9 +35,11 @@
 class ThreadSafeQueue {
  public:
   using QueueImpl = std::deque<T>;
+  using QueueFullHandler = std::function<void(QueueImpl*)>;
+
   ThreadSafeQueue() = default;
   explicit ThreadSafeQueue(std::size_t max_elements,
-                           std::function<void(QueueImpl*)> max_elements_handler)
+                           QueueFullHandler max_elements_handler)
       : max_elements_{max_elements},
         max_elements_handler_{std::move(max_elements_handler)} {}
 
@@ -58,18 +61,17 @@
     return std::move(items_);
   }
 
-  void Push(T&& t) {
+  template <typename U>
+  bool Push(U&& u) {
+    static_assert(std::is_assignable_v<T, decltype(u)>);
     std::lock_guard<std::mutex> guard(m_);
-    DropItemsIfAtCapacity();
-    items_.push_back(std::move(t));
+    const bool has_room = DropItemsIfAtCapacity();
+    if (!has_room) {
+      return false;
+    }
+    items_.push_back(std::forward<U>(u));
     new_item_.notify_one();
-  }
-
-  void Push(const T& t) {
-    std::lock_guard<std::mutex> guard(m_);
-    DropItemsIfAtCapacity();
-    items_.push_back(t);
-    new_item_.notify_one();
+    return true;
   }
 
   bool IsEmpty() {
@@ -83,15 +85,22 @@
   }
 
  private:
-  void DropItemsIfAtCapacity() {
+  // return whether there's room to push
+  bool DropItemsIfAtCapacity() {
     if (max_elements_ && max_elements_ == items_.size()) {
       max_elements_handler_(&items_);
     }
+    if (max_elements_ && max_elements_ == items_.size()) {
+      // handler intends to ignore the newly coming element or
+      // did not empty the room for whatever reason
+      return false;
+    }
+    return true;
   }
 
   std::mutex m_;
   std::size_t max_elements_{};
-  std::function<void(QueueImpl*)> max_elements_handler_{};
+  QueueFullHandler max_elements_handler_{};
   std::condition_variable new_item_;
   QueueImpl items_;
 };
diff --git a/common/libs/confui/Android.bp b/common/libs/confui/Android.bp
index 13ff2fd..ca6348a 100644
--- a/common/libs/confui/Android.bp
+++ b/common/libs/confui/Android.bp
@@ -20,13 +20,16 @@
 cc_library_static {
     name: "libcuttlefish_confui",
     srcs: [
+        "packet_types.cpp",
         "packet.cpp",
+        "protocol_types.cpp",
         "protocol.cpp",
     ],
     static: {
         static_libs: [
             "libbase",
             "libcuttlefish_fs",
+	    "libteeui",
         ],
         shared_libs: [
           "libcrypto", // libcrypto_static is not accessible from all targets
diff --git a/common/libs/confui/confui.h b/common/libs/confui/confui.h
index 957fca1..18dec37 100644
--- a/common/libs/confui/confui.h
+++ b/common/libs/confui/confui.h
@@ -23,15 +23,5 @@
  * header file(s)
  *
  */
-#include "common/libs/confui/packet.h"
 #include "common/libs/confui/protocol.h"
 #include "common/libs/confui/utils.h"
-
-namespace cuttlefish {
-namespace confui {
-using packet::RecvConfUiMsg;
-using packet::SendAck;
-using packet::SendCmd;
-using packet::SendResponse;
-}  // end of namespace confui
-}  // end of namespace cuttlefish
diff --git a/common/libs/confui/packet.cpp b/common/libs/confui/packet.cpp
index d5be03a..debaeb8 100644
--- a/common/libs/confui/packet.cpp
+++ b/common/libs/confui/packet.cpp
@@ -16,82 +16,30 @@
 #include "common/libs/confui/packet.h"
 
 #include <algorithm>
-#include <iostream>
-
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-
-#include "common/libs/confui/protocol.h"
-#include "common/libs/confui/utils.h"
-#include "common/libs/fs/shared_buf.h"
 
 namespace cuttlefish {
 namespace confui {
 namespace packet {
-ConfUiMessage PayloadToConfUiMessage(const std::string& str_to_parse) {
-  auto tokens = android::base::Split(str_to_parse, ":");
-  ConfUiCheck(tokens.size() >= 3)
-      << "PayloadToConfUiMessage takes \"" + str_to_parse + "\""
-      << "and does not have 3 tokens";
-  std::string msg;
-  std::for_each(tokens.begin() + 2, tokens.end() - 1,
-                [&msg](auto& token) { msg.append(token + ":"); });
-  msg.append(*tokens.rbegin());
-  return {tokens[0], tokens[1], msg};
-}
-
-// Use only this function to make a packet to send over the confirmation
-// ui packet layer
-template <typename... Args>
-static Payload ToPayload(const ConfUiCmd cmd, const std::string& session_id,
-                         Args&&... args) {
-  std::string cmd_str = ToString(cmd);
-  std::string msg =
-      ArgsToString(session_id, ":", cmd_str, ":", std::forward<Args>(args)...);
-  PayloadHeader header;
-  header.payload_length_ = msg.size();
-  return {header, msg};
-}
-
-template <typename... Args>
-static bool WritePayload(SharedFD d, const ConfUiCmd cmd,
-                         const std::string& session_id, Args&&... args) {
-  if (!d->IsOpen()) {
-    LOG(ERROR) << "file, socket, etc, is not open to write";
-    return false;
-  }
-  auto [payload, msg] = ToPayload(cmd, session_id, std::forward<Args>(args)...);
-
-  auto nwrite =
-      WriteAll(d, reinterpret_cast<const char*>(&payload), sizeof(payload));
-  if (nwrite != sizeof(payload)) {
-    return false;
-  }
-  nwrite = cuttlefish::WriteAll(d, msg.c_str(), msg.size());
-  if (nwrite != msg.size()) {
-    return false;
-  }
-  return true;
-}
-
-static std::optional<ConfUiMessage> ReadPayload(SharedFD s) {
+static std::optional<std::vector<std::uint8_t>> ReadRawData(SharedFD s) {
   if (!s->IsOpen()) {
-    LOG(ERROR) << "file, socket, etc, is not open to read";
+    ConfUiLog(ERROR) << "file, socket, etc, is not open to read";
     return std::nullopt;
   }
-  PayloadHeader p;
+  packet::PayloadHeader p;
   auto nread = ReadExactBinary(s, &p);
 
   if (nread != sizeof(p)) {
+    ConfUiLog(ERROR) << nread << " and sizeof(p) = " << sizeof(p)
+                     << " not matching";
     return std::nullopt;
   }
-
   if (p.payload_length_ == 0) {
-    return {{SESSION_ANY, ToString(ConfUiCmd::kUnknown), std::string{""}}};
+    return {{}};
   }
 
-  if (p.payload_length_ >= kMaxPayloadLength) {
-    LOG(ERROR) << "Payload length must be less than " << kMaxPayloadLength;
+  if (p.payload_length_ >= packet::kMaxPayloadLength) {
+    ConfUiLog(ERROR) << "Payload length must be less than "
+                     << packet::kMaxPayloadLength;
     return std::nullopt;
   }
 
@@ -99,33 +47,109 @@
   nread = ReadExact(s, buf.get(), p.payload_length_);
   buf[p.payload_length_] = 0;
   if (nread != p.payload_length_) {
+    ConfUiLog(ERROR) << "The length ReadRawData read does not match.";
     return std::nullopt;
   }
-  std::string msg_to_parse{buf.get()};
-  auto [session_id, type, contents] = PayloadToConfUiMessage(msg_to_parse);
-  return {{session_id, type, contents}};
+  std::vector<std::uint8_t> result{buf.get(), buf.get() + nread};
+
+  return {result};
 }
 
-std::optional<ConfUiMessage> RecvConfUiMsg(SharedFD fd) {
-  return ReadPayload(fd);
+static std::optional<ParsedPacket> ParseRawData(
+    const std::vector<std::uint8_t>& data_to_parse) {
+  /*
+   * data_to_parse has 0 in it, so it is not exactly "your (text) std::string."
+   * If we type-cast data_to_parse to std::string and use 3rd party std::string-
+   * processing libraries, the outcome might be incorrect. However, the header
+   * part has no '\0' in it, and is actually a sequence of letters, or a text.
+   * So, we use android::base::Split() to take the header
+   *
+   */
+  std::string as_string{data_to_parse.begin(), data_to_parse.end()};
+  auto tokens = android::base::Split(as_string, ":");
+  CHECK(tokens.size() >= 3)
+      << "Raw packet for confirmation UI must have at least"
+      << " three components.";
+  /**
+   * Here is how the raw data, i.e. tokens[2:] looks like
+   *
+   * n:l[0]:l[1]:l[2]:...:l[n-1]:data[0]data[1]data[2]...data[n]
+   *
+   * Thus it basically has the number of items, the lengths of each item,
+   * and the byte representation of each item. n and l[i] are separated by ':'
+   * Note that the byte representation may have ':' in it. This could mess
+   * up the parsing if we totally depending on ':' separation.
+   *
+   * However, it is safe to assume that there's no ':' inside n or
+   * the string for l[i]. So, we do anyway split the data_to_parse by ':',
+   * and take n and from l[0] through l[n-1] only.
+   */
+  std::string session_id = tokens[0];
+  std::string cmd_type = tokens[1];
+  if (!IsOnlyDigits(tokens[2])) {
+    ConfUiLog(ERROR) << "Token[2] of the ConfUi packet should be a number";
+    return std::nullopt;
+  }
+  const int n = std::stoi(tokens[2]);
+
+  if (n + 2 > tokens.size()) {
+    ConfUiLog(ERROR) << "The ConfUi packet is ill-formatted.";
+    return std::nullopt;
+  }
+  ConfUiPacketInfo data_to_return;
+  std::vector<int> lengths;
+  for (int i = 1; i <= n; i++) {
+    if (!IsOnlyDigits(tokens[2 + i])) {
+      ConfUiLog(ERROR) << tokens[2 + i] << " should be a number but is not.";
+      return std::nullopt;
+    }
+    lengths.emplace_back(std::stoi(tokens[2 + i]));
+  }
+  // to find the first position of the non-header part
+  int pos = 0;
+  // 3 for three ":"s
+  pos += tokens[0].size() + tokens[1].size() + tokens[2].size() + 3;
+  for (int i = 1; i <= n; i++) {
+    pos += tokens[2 + i].size() + 1;
+  }
+  int expected_total_length = pos;
+  for (auto const len : lengths) {
+    expected_total_length += len;
+  }
+  if (expected_total_length != data_to_parse.size()) {
+    ConfUiLog(ERROR) << "expected length in ParseRawData is "
+                     << expected_total_length << " while the actual length is "
+                     << data_to_parse.size();
+    return std::nullopt;
+  }
+  for (const auto len : lengths) {
+    if (len == 0) {
+      // push null vector or whatever empty, appropriately-typed
+      // container
+      data_to_return.emplace_back(std::vector<std::uint8_t>{});
+      continue;
+    }
+    data_to_return.emplace_back(data_to_parse.begin() + pos,
+                                data_to_parse.begin() + pos + len);
+    pos = pos + len;
+  }
+  ParsedPacket result{session_id, cmd_type, data_to_return};
+  return {result};
 }
 
-bool SendCmd(SharedFD fd, const std::string& session_id, ConfUiCmd cmd,
-             const std::string& additional_info) {
-  return WritePayload(fd, cmd, session_id, additional_info);
+std::optional<ParsedPacket> ReadPayload(SharedFD s) {
+  auto raw_data = ReadRawData(s);
+  if (!raw_data) {
+    ConfUiLog(ERROR) << "raw data returned std::nullopt";
+    return std::nullopt;
+  }
+  auto parsed_result = ParseRawData(raw_data.value());
+  if (!parsed_result) {
+    ConfUiLog(ERROR) << "parsed result returns nullopt";
+    return std::nullopt;
+  }
+  return parsed_result;
 }
-
-bool SendAck(SharedFD fd, const std::string& session_id, const bool is_success,
-             const std::string& additional_info) {
-  return WritePayload(fd, ConfUiCmd::kCliAck, session_id,
-                      ToCliAckMessage(is_success, additional_info));
-}
-
-bool SendResponse(SharedFD fd, const std::string& session_id,
-                  const std::string& additional_info) {
-  return WritePayload(fd, ConfUiCmd::kCliRespond, session_id, additional_info);
-}
-
 }  // end of namespace packet
 }  // end of namespace confui
 }  // end of namespace cuttlefish
diff --git a/common/libs/confui/packet.h b/common/libs/confui/packet.h
index 848eb29..e109a61 100644
--- a/common/libs/confui/packet.h
+++ b/common/libs/confui/packet.h
@@ -15,56 +15,122 @@
 
 #pragma once
 
+#include <algorithm>
 #include <cstdint>
-#include <functional>
 #include <optional>
 #include <string>
 #include <tuple>
+#include <type_traits>
+#include <vector>
 
-#include "common/libs/confui/protocol.h"
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "common/libs/confui/packet_types.h"
+#include "common/libs/confui/utils.h"
+#include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
 
+/**
+ * @file packet.h
+ *
+ * @brief lowest-level packet for communication between host & guest
+ *
+ * Each packet has three fields
+ *  1. session_id_: the name of the currently active confirmation UI session
+ *  2. type_: the type of command/response. E.g. start, stop, ack, abort, etc
+ *  3. additional_info_: all the other additional information
+ *
+ * The binary represenation of each packet is as follows:
+ *  n:L[1]:L[2]:...:L[n]:data[1]data[2]data[3]...data[n]
+ *
+ * The additional_info_ is in general a variable number of items, each
+ * is either a byte vector (e.g. std::vector<uint8_t>) or a string.
+ *
+ * n is the number of items. L[i] is the length of i th item. data[i]
+ * is the binary representation of the i th item
+ *
+ */
 namespace cuttlefish {
 namespace confui {
 namespace packet {
+
 /*
- * for communication between Confirmation UI guest and host.
+ * methods in namespace impl is not intended for public use
  *
- * Payload is actually the header. When we send/recv, besides Payload,
- * the "payload_length_" bytes should be additionally sent/recv'ed.
- *
- * The payload is assumed to be a text (e.g. char[N])
- * The WritePayload will create the string. When read, however,
- * the receiver should parse it
- *
- * The format we use for confirmation UI is:
- *  session_id:type:contents
- *
- * e.g. GooglePay10354:start:my confirmaton message
+ * For exposed APIs, skip to "start of public APIs
+ * or, skip the namespace impl
  */
-struct PayloadHeader {
-  std::uint32_t payload_length_;
-};
+namespace impl {
+template <typename Buffer, typename... Args>
+void AppendToBuffer(Buffer& buffer, Args&&... args) {
+  (buffer.insert(buffer.end(), std::begin(std::forward<Args>(args)),
+                 std::end(std::forward<Args>(args))),
+   ...);
+}
 
-// PayloadHeader + the message actually being sent
-using Payload = std::tuple<PayloadHeader, std::string>;
+template <typename... Args>
+std::vector<int> MakeSizeHeader(Args&&... args) {
+  std::vector<int> lengths;
+  (lengths.push_back(std::distance(std::begin(args), std::end(args))), ...);
+  return lengths;
+}
 
-// msg will look like "334522:start:Hello I am Here!"
-// this function returns 334522, start, "Hello I am Here!"
-// if no session id is given, it is regarded as SESSION_ANY
-ConfUiMessage PayloadToConfUiMessage(const std::string& str_to_parse);
+// Use only this function to make a packet to send over the confirmation
+// ui packet layer
+template <typename... Args>
+Payload ToPayload(const std::string& cmd_str, const std::string& session_id,
+                  Args&&... args) {
+  using namespace cuttlefish::confui::packet::impl;
+  constexpr auto n_args = sizeof...(Args);
+  std::stringstream ss;
+  ss << ArgsToString(session_id, ":", cmd_str, ":", n_args, ":");
+  // create size header
+  std::vector<int> size_info =
+      impl::MakeSizeHeader(std::forward<Args>(args)...);
+  for (const auto sz : size_info) {
+    ss << sz << ":";
+  }
+  std::string header = ss.str();
+  std::vector<std::uint8_t> payload_buffer{header.begin(), header.end()};
+  impl::AppendToBuffer(payload_buffer, std::forward<Args>(args)...);
 
-std::optional<ConfUiMessage> RecvConfUiMsg(SharedFD fd);
-bool SendAck(SharedFD fd, const std::string& session_id, const bool is_success,
-             const std::string& additional_info);
-bool SendResponse(SharedFD fd, const std::string& session_id,
-                  const std::string& additional_info);
-// for HAL
-bool SendCmd(SharedFD fd, const std::string& session_id, ConfUiCmd cmd,
-             const std::string& additional_info);
+  PayloadHeader ph;
+  ph.payload_length_ = payload_buffer.size();
+  return {ph, payload_buffer};
+}
+}  // namespace impl
 
-// this is for short messages
-constexpr const ssize_t kMaxPayloadLength = 1000;
+/*
+ * start of public methods
+ */
+std::optional<ParsedPacket> ReadPayload(SharedFD s);
+
+template <typename... Args>
+bool WritePayload(SharedFD d, const std::string& cmd_str,
+                  const std::string& session_id, Args&&... args) {
+  // TODO(kwstephenkim): type check Args... so that they are either
+  // kind of std::string or std::vector<1 byte>
+  if (!d->IsOpen()) {
+    ConfUiLog(ERROR) << "file, socket, etc, is not open to write";
+    return false;
+  }
+  auto [payload_header, data_to_send] =
+      impl::ToPayload(cmd_str, session_id, std::forward<Args>(args)...);
+  const std::string data_in_str(data_to_send.cbegin(), data_to_send.cend());
+
+  auto nwrite = WriteAll(d, reinterpret_cast<const char*>(&payload_header),
+                         sizeof(payload_header));
+  if (nwrite != sizeof(payload_header)) {
+    return false;
+  }
+  nwrite = WriteAll(d, reinterpret_cast<const char*>(data_to_send.data()),
+                    data_to_send.size());
+  if (nwrite != data_to_send.size()) {
+    return false;
+  }
+  return true;
+}
 
 }  // end of namespace packet
 }  // end of namespace confui
diff --git a/common/libs/confui/packet_types.cpp b/common/libs/confui/packet_types.cpp
new file mode 100644
index 0000000..f3108b5
--- /dev/null
+++ b/common/libs/confui/packet_types.cpp
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "common/libs/confui/packet_types.h"
+
+#include <sstream>
+
+namespace cuttlefish {
+namespace confui {
+namespace packet {
+std::string ToString(const ParsedPacket& packet) {
+  std::stringstream ss;
+  ss << "[" << packet.session_id_ << "," << packet.type_ << ",";
+  for (auto const& vec : packet.additional_info_) {
+    if (vec.empty()) {
+      ss << ",";
+      continue;
+    }
+    std::string token(vec.cbegin(), vec.cend());
+    ss << token << ",";
+  }
+  std::string result = ss.str();
+  bool is_remove_one_comma = (!packet.additional_info_.empty());
+  if (is_remove_one_comma) {
+    result.pop_back();
+  }
+  result.append("]");
+  return result;
+}
+}  // end of namespace packet
+}  // end of namespace confui
+}  // end of namespace cuttlefish
diff --git a/common/libs/confui/packet_types.h b/common/libs/confui/packet_types.h
new file mode 100644
index 0000000..73f2745
--- /dev/null
+++ b/common/libs/confui/packet_types.h
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <cstdint>
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace cuttlefish {
+namespace confui {
+namespace packet {
+struct PayloadHeader {
+  std::uint32_t payload_length_;
+};
+
+using BufferType = std::vector<std::uint8_t>;
+
+// PayloadHeader + the byte size sent over the channel
+using Payload = std::tuple<PayloadHeader, BufferType>;
+
+// this is for short messages
+constexpr const ssize_t kMaxPayloadLength = 10000;
+
+using ConfUiPacketInfo = std::vector<std::vector<std::uint8_t>>;
+struct ParsedPacket {
+  std::string session_id_;
+  std::string type_;
+  ConfUiPacketInfo additional_info_;
+};
+
+std::string ToString(const ParsedPacket& packet);
+}  // end of namespace packet
+}  // end of namespace confui
+}  // end of namespace cuttlefish
diff --git a/common/libs/confui/protocol.cpp b/common/libs/confui/protocol.cpp
index 6784034..03ca4cc 100644
--- a/common/libs/confui/protocol.cpp
+++ b/common/libs/confui/protocol.cpp
@@ -15,88 +15,259 @@
 
 #include "common/libs/confui/protocol.h"
 
-#include <map>
 #include <sstream>
-#include <unordered_map>
 #include <vector>
 
 #include <android-base/strings.h>
 
+#include "common/libs/confui/packet.h"
 #include "common/libs/confui/utils.h"
+#include "common/libs/fs/shared_buf.h"
 
 namespace cuttlefish {
 namespace confui {
-std::string ToDebugString(const ConfUiCmd& cmd, const bool is_debug) {
-  std::stringstream ss;
-  ss << "of " << Enum2Base(cmd);
-  std::string suffix = "";
-  if (is_debug) {
-    suffix.append(ss.str());
-  }
-  static std::unordered_map<ConfUiCmd, std::string> look_up_tab{
-      {ConfUiCmd::kUnknown, "kUnknown"},
-      {ConfUiCmd::kStart, "kStart"},
-      {ConfUiCmd::kStop, "kStop"},
-      {ConfUiCmd::kCliAck, "kCliAck"},
-      {ConfUiCmd::kCliRespond, "kCliRespond"},
-      {ConfUiCmd::kAbort, "kAbort"},
-      {ConfUiCmd::kSuspend, "kSuspend"},
-      {ConfUiCmd::kRestore, "kRestore"},
-      {ConfUiCmd::kUserInputEvent, "kUserInputEvent"}};
-  if (look_up_tab.find(cmd) != look_up_tab.end()) {
-    return look_up_tab[cmd] + suffix;
-  }
-  return "kUnknown" + suffix;
+namespace {
+// default implementation of ToConfUiMessage
+template <ConfUiCmd C>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage(
+    const packet::ParsedPacket& message) {
+  return std::make_unique<ConfUiGenericMessage<C>>(message.session_id_);
 }
 
-std::string ToString(const ConfUiCmd& cmd) { return ToDebugString(cmd, false); }
+// these are specialized, and defined below
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kCliAck>(
+    const packet::ParsedPacket& message);
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kStart>(
+    const packet::ParsedPacket& message);
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kUserInputEvent>(
+    const packet::ParsedPacket& message);
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kUserTouchEvent>(
+    const packet::ParsedPacket& message);
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kCliRespond>(
+    const packet::ParsedPacket& message);
 
-ConfUiCmd ToCmd(std::uint32_t i) {
-  std::vector<ConfUiCmd> all_cmds{
-      ConfUiCmd::kStart,      ConfUiCmd::kStop,           ConfUiCmd::kCliAck,
-      ConfUiCmd::kCliRespond, ConfUiCmd::kAbort,          ConfUiCmd::kSuspend,
-      ConfUiCmd::kRestore,    ConfUiCmd::kUserInputEvent, ConfUiCmd::kUnknown};
+std::unique_ptr<ConfUiMessage> ToConfUiMessage(
+    const packet::ParsedPacket& confui_packet) {
+  const auto confui_cmd = ToCmd(confui_packet.type_);
+  switch (confui_cmd) {
+    // customized ConfUiMessage
+    case ConfUiCmd::kStart:
+      return ToConfUiMessage<ConfUiCmd::kStart>(confui_packet);
+    case ConfUiCmd::kCliAck:
+      return ToConfUiMessage<ConfUiCmd::kCliAck>(confui_packet);
+    case ConfUiCmd::kCliRespond:
+      return ToConfUiMessage<ConfUiCmd::kCliRespond>(confui_packet);
+    case ConfUiCmd::kUserInputEvent:
+      return ToConfUiMessage<ConfUiCmd::kUserInputEvent>(confui_packet);
+    case ConfUiCmd::kUserTouchEvent:
+      return ToConfUiMessage<ConfUiCmd::kUserTouchEvent>(confui_packet);
+      // default ConfUiMessage with session & type only
+    case ConfUiCmd::kAbort:
+      return ToConfUiMessage<ConfUiCmd::kAbort>(confui_packet);
+    case ConfUiCmd::kStop:
+      return ToConfUiMessage<ConfUiCmd::kStop>(confui_packet);
+      // these are errors
+    case ConfUiCmd::kUnknown:
+    default:
+      ConfUiLog(ERROR) << "ConfUiCmd value is not good for ToConfUiMessage: "
+                       << ToString(confui_cmd);
+      break;
+  }
+  return {nullptr};
+}
+}  // end of unnamed namespace
 
-  for (auto& cmd : all_cmds) {
-    if (i == Enum2Base(cmd)) {
-      return cmd;
+std::string ToString(const ConfUiMessage& msg) { return msg.ToString(); }
+
+std::unique_ptr<ConfUiMessage> RecvConfUiMsg(SharedFD fd) {
+  if (!fd->IsOpen()) {
+    ConfUiLog(ERROR) << "file, socket, etc, is not open to read";
+    return {nullptr};
+  }
+  auto confui_packet_opt = packet::ReadPayload(fd);
+  if (!confui_packet_opt) {
+    ConfUiLog(ERROR) << "ReadPayload returns but with std::nullptr";
+    return {nullptr};
+  }
+
+  auto confui_packet = confui_packet_opt.value();
+  return ToConfUiMessage(confui_packet);
+}
+
+std::unique_ptr<ConfUiMessage> RecvConfUiMsg(const std::string& session_id,
+                                             SharedFD fd) {
+  auto conf_ui_msg = RecvConfUiMsg(fd);
+  if (!conf_ui_msg) {
+    return {nullptr};
+  }
+  auto recv_session_id = conf_ui_msg->GetSessionId();
+  if (session_id != recv_session_id) {
+    ConfUiLog(ERROR) << "Received Session ID (" << recv_session_id
+                     << ") is not the expected one (" << session_id << ")";
+    return {nullptr};
+  }
+  return conf_ui_msg;
+}
+
+bool SendAbortCmd(SharedFD fd, const std::string& session_id) {
+  ConfUiGenericMessage<ConfUiCmd::kAbort> confui_msg{session_id};
+  return confui_msg.SendOver(fd);
+}
+
+bool SendStopCmd(SharedFD fd, const std::string& session_id) {
+  ConfUiGenericMessage<ConfUiCmd::kStop> confui_msg{session_id};
+  return confui_msg.SendOver(fd);
+}
+
+bool SendAck(SharedFD fd, const std::string& session_id, const bool is_success,
+             const std::string& status_message) {
+  ConfUiAckMessage confui_msg{session_id, is_success, status_message};
+  return confui_msg.SendOver(fd);
+}
+
+bool SendResponse(SharedFD fd, const std::string& session_id,
+                  const UserResponse::type& plain_selection,
+                  const std::vector<std::uint8_t>& signed_response,
+                  const std::vector<std::uint8_t>& message) {
+  ConfUiCliResponseMessage confui_msg{session_id, plain_selection,
+                                      signed_response, message};
+  return confui_msg.SendOver(fd);
+}
+
+bool SendStartCmd(SharedFD fd, const std::string& session_id,
+                  const std::string& prompt_text,
+                  const std::vector<std::uint8_t>& extra_data,
+                  const std::string& locale,
+                  const std::vector<teeui::UIOption>& ui_opts) {
+  ConfUiStartMessage confui_msg{session_id, prompt_text, extra_data, locale,
+                                ui_opts};
+  return confui_msg.SendOver(fd);
+}
+
+// this is only for deliverSecureInputEvent
+bool SendUserSelection(SharedFD fd, const std::string& session_id,
+                       const UserResponse::type& confirm_cancel) {
+  ConfUiUserSelectionMessage confui_msg{session_id, confirm_cancel};
+  return confui_msg.SendOver(fd);
+}
+
+// specialized ToConfUiMessage()
+namespace {
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kCliAck>(
+    const packet::ParsedPacket& message) {
+  auto type = ToCmd(message.type_);
+  auto& contents = message.additional_info_;
+  if (type != ConfUiCmd::kCliAck) {
+    ConfUiLog(ERROR) << "Received cmd is not ack but " << ToString(type);
+    return {nullptr};
+  }
+
+  if (contents.size() != 2) {
+    ConfUiLog(ERROR)
+        << "Ack message should only have pass/fail and a status message";
+    return {nullptr};
+  }
+
+  const std::string success_str(contents[0].begin(), contents[0].end());
+  const bool is_success = (success_str == "success");
+  const std::string status_message(contents[1].begin(), contents[1].end());
+  return std::make_unique<ConfUiAckMessage>(message.session_id_, is_success,
+                                            status_message);
+}
+
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kStart>(
+    const packet::ParsedPacket& message) {
+  /*
+   * additional_info_[0]: prompt text
+   * additional_info_[1]: extra data
+   * additional_info_[2]: locale
+   * additional_info_[3]: UIOptions
+   *
+   */
+  if (message.additional_info_.size() < 3) {
+    ConfUiLog(ERROR) << "ConfUiMessage for kStart is ill-formatted: "
+                     << packet::ToString(message);
+    return {nullptr};
+  }
+  std::vector<teeui::UIOption> ui_opts;
+  bool has_ui_option = (message.additional_info_.size() == 4) &&
+                       !(message.additional_info_[3].empty());
+  if (has_ui_option) {
+    std::string ui_opts_string{message.additional_info_[3].begin(),
+                               message.additional_info_[3].end()};
+    auto tokens = android::base::Split(ui_opts_string, ",");
+    for (auto token : tokens) {
+      auto ui_opt_optional = ToUiOption(token);
+      if (!ui_opt_optional) {
+        ConfUiLog(ERROR) << "Wrong UiOption String : " << token;
+        return {nullptr};
+      }
+      ui_opts.emplace_back(ui_opt_optional.value());
     }
   }
-  return ConfUiCmd::kUnknown;
+  auto sm = std::make_unique<ConfUiStartMessage>(
+      message.session_id_,
+      std::string(message.additional_info_[0].begin(),
+                  message.additional_info_[0].end()),
+      message.additional_info_[1],
+      std::string(message.additional_info_[2].begin(),
+                  message.additional_info_[2].end()),
+      ui_opts);
+  return sm;
 }
 
-ConfUiCmd ToCmd(const std::string& cmd_str) {
-  static std::map<std::string, ConfUiCmd> cmds = {
-      {"kStart", ConfUiCmd::kStart},
-      {"kStop", ConfUiCmd::kStop},
-      {"kCliAck", ConfUiCmd::kCliAck},
-      {"kCliRespond", ConfUiCmd::kCliRespond},
-      {"kAbort", ConfUiCmd::kAbort},
-      {"kSuspend", ConfUiCmd::kSuspend},
-      {"kRestore", ConfUiCmd::kRestore},
-      {"kUserInputEvent", ConfUiCmd::kUserInputEvent},
-  };
-  if (cmds.find(cmd_str) != cmds.end()) {
-    return cmds[cmd_str];
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kUserInputEvent>(
+    const packet::ParsedPacket& message) {
+  if (message.additional_info_.size() < 1) {
+    ConfUiLog(ERROR)
+        << "kUserInputEvent message should have at least one additional_info_";
+    return {nullptr};
   }
-  return ConfUiCmd::kUnknown;
+  auto response = std::string{message.additional_info_[0].begin(),
+                              message.additional_info_[0].end()};
+  return std::make_unique<ConfUiUserSelectionMessage>(message.session_id_,
+                                                      response);
 }
 
-std::string ToCliAckMessage(const bool is_success, const std::string& message) {
-  std::string header = "error:";
-  if (is_success) {
-    header = "success:";
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kUserTouchEvent>(
+    const packet::ParsedPacket& message) {
+  if (message.additional_info_.size() < 2) {
+    ConfUiLog(ERROR)
+        << "kUserTouchEvent message should have at least two additional_info_";
+    return {nullptr};
   }
-  return header + message;
+  auto x = std::string(message.additional_info_[0].begin(),
+                       message.additional_info_[0].end());
+  auto y = std::string(message.additional_info_[1].begin(),
+                       message.additional_info_[1].end());
+  return std::make_unique<ConfUiUserTouchMessage>(message.session_id_,
+                                                  std::stoi(x), std::stoi(y));
 }
 
-std::string ToCliAckSuccessMsg(const std::string& message) {
-  return ToCliAckMessage(true, message);
+template <>
+std::unique_ptr<ConfUiMessage> ToConfUiMessage<ConfUiCmd::kCliRespond>(
+    const packet::ParsedPacket& message) {
+  if (message.additional_info_.size() < 3) {
+    ConfUiLog(ERROR)
+        << "kCliRespond message should have at least two additional info";
+    return {nullptr};
+  }
+  auto response = std::string{message.additional_info_[0].begin(),
+                              message.additional_info_[0].end()};
+  auto sign = message.additional_info_[1];
+  auto msg = message.additional_info_[2];
+  return std::make_unique<ConfUiCliResponseMessage>(message.session_id_,
+                                                    response, sign, msg);
 }
-
-std::string ToCliAckErrorMsg(const std::string& message) {
-  return ToCliAckMessage(false, message);
-}
-
+}  // end of unnamed namespace
 }  // end of namespace confui
 }  // end of namespace cuttlefish
diff --git a/common/libs/confui/protocol.h b/common/libs/confui/protocol.h
index 582b43e..f41afc4 100644
--- a/common/libs/confui/protocol.h
+++ b/common/libs/confui/protocol.h
@@ -16,48 +16,55 @@
 #pragma once
 
 #include <cstdint>
+#include <optional>
 #include <string>
+#include <tuple>
+
+#include <teeui/common_message_types.h>  // /system/teeui/libteeui/.../include
+
+#include "common/libs/confui/packet_types.h"
+#include "common/libs/confui/protocol_types.h"
+#include "common/libs/fs/shared_fd.h"
 
 namespace cuttlefish {
 namespace confui {
-// When you update this, please update all the utility functions
-// in conf.cpp: e.g. ToString, etc
-enum class ConfUiCmd : std::uint32_t {
-  kUnknown = 100,
-  kStart = 111,   // start rendering, send confirmation msg, & wait respond
-  kStop = 112,    // start rendering, send confirmation msg, & wait respond
-  kCliAck = 113,  // client acknowledged. "error:err_msg" or "success:command"
-  kCliRespond = 114,  //  with "confirm" or "cancel"
-  kAbort = 115,       // to abort the current session
-  kSuspend = 116,     // to suspend, so do save the context
-  kRestore = 117,
-  kUserInputEvent = 200
-};
 
-std::string ToString(const ConfUiCmd& cmd);
-std::string ToDebugString(const ConfUiCmd& cmd, const bool is_debug);
-ConfUiCmd ToCmd(const std::string& cmd_str);
-ConfUiCmd ToCmd(std::uint32_t i);
+std::string ToString(const ConfUiMessage& msg);
 
-struct UserResponse {
-  using type = std::string;
-  constexpr static const auto kConfirm = "user_confirm";
-  constexpr static const auto kCancel = "user_cancel";
-  constexpr static const auto kUnknown = "user_unknown";
-};
+constexpr auto SESSION_ANY = "";
+/*
+ * received confirmation UI message on the guest could be abort or
+ * ack/response. Thus, the guest APIs should call RecvConfUiMsg(fd),
+ * see which is it, and then use Into*(conf_ui_message) to
+ * parse & use it.
+ *
+ */
+std::unique_ptr<ConfUiMessage> RecvConfUiMsg(SharedFD fd);
+std::unique_ptr<ConfUiMessage> RecvConfUiMsg(const std::string& session_id,
+                                             SharedFD fd);
 
-// invalid/ignored session id
-constexpr char SESSION_ANY[] = "";
+bool SendAbortCmd(SharedFD fd, const std::string& session_id);
 
-std::string ToCliAckMessage(const bool is_success, const std::string& message);
-std::string ToCliAckErrorMsg(const std::string& message);
-std::string ToCliAckSuccessMsg(const std::string& message);
+bool SendAck(SharedFD fd, const std::string& session_id, const bool is_success,
+             const std::string& status_message);
+bool SendResponse(SharedFD fd, const std::string& session_id,
+                  const UserResponse::type& plain_selection,
+                  const std::vector<std::uint8_t>& signed_response,
+                  // signing is a function of message, key
+                  const std::vector<std::uint8_t>& message);
 
-struct ConfUiMessage {
-  std::string session_id_;
-  std::string type_;  // cmd, which cmd? ack, response, etc
-  std::string msg_;
-};
+// for HAL
+bool SendStartCmd(SharedFD fd, const std::string& session_id,
+                  const std::string& prompt_text,
+                  const std::vector<std::uint8_t>& extra_data,
+                  const std::string& locale,
+                  const std::vector<teeui::UIOption>& ui_opts);
+
+bool SendStopCmd(SharedFD fd, const std::string& session_id);
+
+// for HAL::deliverSecureInputEvent
+bool SendUserSelection(SharedFD fd, const std::string& session_id,
+                       const UserResponse::type& confirm_cancel);
 
 }  // end of namespace confui
 }  // end of namespace cuttlefish
diff --git a/common/libs/confui/protocol_types.cpp b/common/libs/confui/protocol_types.cpp
new file mode 100644
index 0000000..8c293ea
--- /dev/null
+++ b/common/libs/confui/protocol_types.cpp
@@ -0,0 +1,174 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "common/libs/confui/protocol_types.h"
+
+#include <map>
+#include <sstream>
+#include <unordered_map>
+
+#include "common/libs/confui/packet.h"
+#include "common/libs/confui/utils.h"
+
+namespace cuttlefish {
+namespace confui {
+std::string ToDebugString(const ConfUiCmd& cmd, const bool is_verbose) {
+  std::stringstream ss;
+  ss << " of " << Enum2Base(cmd);
+  std::string suffix = "";
+  if (is_verbose) {
+    suffix.append(ss.str());
+  }
+  static std::unordered_map<ConfUiCmd, std::string> look_up_tab{
+      {ConfUiCmd::kUnknown, "kUnknown"},
+      {ConfUiCmd::kStart, "kStart"},
+      {ConfUiCmd::kStop, "kStop"},
+      {ConfUiCmd::kCliAck, "kCliAck"},
+      {ConfUiCmd::kCliRespond, "kCliRespond"},
+      {ConfUiCmd::kAbort, "kAbort"},
+      {ConfUiCmd::kUserInputEvent, "kUserInputEvent"},
+      {ConfUiCmd::kUserInputEvent, "kUserTouchEvent"}};
+  if (look_up_tab.find(cmd) != look_up_tab.end()) {
+    return look_up_tab[cmd] + suffix;
+  }
+  return "kUnknown" + suffix;
+}
+
+std::string ToString(const ConfUiCmd& cmd) { return ToDebugString(cmd, false); }
+
+ConfUiCmd ToCmd(std::uint32_t i) {
+  std::vector<ConfUiCmd> all_cmds{
+      ConfUiCmd::kStart,          ConfUiCmd::kStop,
+      ConfUiCmd::kCliAck,         ConfUiCmd::kCliRespond,
+      ConfUiCmd::kAbort,          ConfUiCmd::kUserInputEvent,
+      ConfUiCmd::kUserTouchEvent, ConfUiCmd::kUnknown};
+
+  for (auto& cmd : all_cmds) {
+    if (i == Enum2Base(cmd)) {
+      return cmd;
+    }
+  }
+  return ConfUiCmd::kUnknown;
+}
+
+ConfUiCmd ToCmd(const std::string& cmd_str) {
+  static std::map<std::string, ConfUiCmd> cmds = {
+      {"kStart", ConfUiCmd::kStart},
+      {"kStop", ConfUiCmd::kStop},
+      {"kCliAck", ConfUiCmd::kCliAck},
+      {"kCliRespond", ConfUiCmd::kCliRespond},
+      {"kAbort", ConfUiCmd::kAbort},
+      {"kUserInputEvent", ConfUiCmd::kUserInputEvent},
+      {"kUserTouchEvent", ConfUiCmd::kUserTouchEvent},
+  };
+  if (cmds.find(cmd_str) != cmds.end()) {
+    return cmds[cmd_str];
+  }
+  return ConfUiCmd::kUnknown;
+}
+
+std::string ToString(const teeui::UIOption ui_opt) {
+  return std::to_string(static_cast<int>(ui_opt));
+}
+
+std::optional<teeui::UIOption> ToUiOption(const std::string& src) {
+  if (!IsOnlyDigits(src)) {
+    return std::nullopt;
+  }
+  return {static_cast<teeui::UIOption>(std::stoi(src))};
+}
+
+template <typename T>
+static std::string ByteVecToString(const std::vector<T>& v) {
+  static_assert(sizeof(T) == 1);
+  std::string result{v.begin(), v.end()};
+  return result;
+}
+
+bool ConfUiMessage::IsUserInput() const {
+  switch (GetType()) {
+    case ConfUiCmd::kUserInputEvent:
+    case ConfUiCmd::kUserTouchEvent:
+      return true;
+    default:
+      return false;
+  }
+}
+
+std::string ConfUiAckMessage::ToString() const {
+  return CreateString(session_id_, confui::ToString(GetType()),
+                      (is_success_ ? "success" : "fail"), status_message_);
+}
+
+bool ConfUiAckMessage::SendOver(SharedFD fd) {
+  return Send_(fd, GetType(), session_id_,
+               std::string(is_success_ ? "success" : "fail"), status_message_);
+}
+
+std::string ConfUiCliResponseMessage::ToString() const {
+  return CreateString(session_id_, confui::ToString(GetType()), response_,
+                      ByteVecToString(sign_), ByteVecToString(message_));
+}
+
+bool ConfUiCliResponseMessage::SendOver(SharedFD fd) {
+  return Send_(fd, GetType(), session_id_, response_, sign_, message_);
+}
+
+std::string ConfUiStartMessage::UiOptsToString() const {
+  std::stringstream ss;
+  for (const auto& ui_opt : ui_opts_) {
+    ss << cuttlefish::confui::ToString(ui_opt) << ",";
+  }
+  auto ui_opt_str = ss.str();
+  if (!ui_opt_str.empty()) {
+    ui_opt_str.pop_back();
+  }
+  return ui_opt_str;
+}
+
+std::string ConfUiStartMessage::ToString() const {
+  auto ui_opts_str = UiOptsToString();
+  return CreateString(
+      session_id_, confui::ToString(GetType()), prompt_text_, locale_,
+      std::string(extra_data_.begin(), extra_data_.end()), ui_opts_str);
+}
+
+bool ConfUiStartMessage::SendOver(SharedFD fd) {
+  return Send_(fd, GetType(), session_id_, prompt_text_, extra_data_, locale_,
+               UiOptsToString());
+}
+
+std::string ConfUiUserSelectionMessage::ToString() const {
+  return CreateString(session_id_, confui::ToString(GetType()), response_);
+}
+
+bool ConfUiUserSelectionMessage::SendOver(SharedFD fd) {
+  return Send_(fd, GetType(), session_id_, response_);
+}
+
+std::string ConfUiUserTouchMessage::ToString() const {
+  std::stringstream ss;
+  ss << "(" << x_ << "," << y_ << ")";
+  auto pos = ss.str();
+  return CreateString(session_id_, confui::ToString(GetType()), response_, pos);
+}
+
+bool ConfUiUserTouchMessage::SendOver(SharedFD fd) {
+  return Send_(fd, GetType(), session_id_, std::to_string(x_),
+               std::to_string(y_));
+}
+
+}  // end of namespace confui
+}  // end of namespace cuttlefish
diff --git a/common/libs/confui/protocol_types.h b/common/libs/confui/protocol_types.h
new file mode 100644
index 0000000..98f581f
--- /dev/null
+++ b/common/libs/confui/protocol_types.h
@@ -0,0 +1,232 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <teeui/common_message_types.h>  // /system/teeui/libteeui/.../include
+
+#include "common/libs/confui/packet.h"
+#include "common/libs/confui/packet_types.h"
+
+#include "common/libs/confui/utils.h"
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+namespace confui {
+// When you update this, please update all the utility functions
+// in conf.cpp: e.g. ToString, etc
+enum class ConfUiCmd : std::uint32_t {
+  kUnknown = 100,
+  kStart = 111,   // start rendering, send confirmation msg, & wait respond
+  kStop = 112,    // start rendering, send confirmation msg, & wait respond
+  kCliAck = 113,  // client acknowledged. "error:err_msg" or "success:command"
+  kCliRespond = 114,  //  with "confirm" or "cancel" or "abort"
+  kAbort = 115,       // to abort the current session
+  kUserInputEvent = 200,
+  kUserTouchEvent = 201
+};
+
+// this is for short messages
+constexpr const ssize_t kMaxMessageLength = packet::kMaxPayloadLength;
+
+std::string ToString(const ConfUiCmd& cmd);
+std::string ToDebugString(const ConfUiCmd& cmd, const bool is_debug);
+ConfUiCmd ToCmd(const std::string& cmd_str);
+ConfUiCmd ToCmd(std::uint32_t i);
+
+std::string ToString(const teeui::UIOption ui_opt);
+std::optional<teeui::UIOption> ToUiOption(const std::string&);
+
+struct HostError {
+  static constexpr char kSystemError[] = "system_error";
+  static constexpr char kUIError[] = "ui_error";
+  static constexpr char kMessageTooLongError[] = "msg_too_long_error";
+  static constexpr char kIncorrectUTF8[] = "msg_incorrect_utf8";
+};
+
+struct UserResponse {
+  using type = std::string;
+  constexpr static const auto kConfirm = "user_confirm";
+  constexpr static const auto kCancel = "user_cancel";
+  constexpr static const auto kTouchEvent = "user_touch";
+  // user may close x button on the virtual window or so
+  // or.. scroll the session up and throw to trash bin
+  constexpr static const auto kUserAbort = "user_abort";
+  constexpr static const auto kUnknown = "user_unknown";
+};
+
+class ConfUiMessage {
+ public:
+  ConfUiMessage(const std::string& session_id) : session_id_{session_id} {}
+  virtual ~ConfUiMessage() = default;
+  virtual std::string ToString() const = 0;
+  void SetSessionId(const std::string session_id) { session_id_ = session_id; }
+  std::string GetSessionId() const { return session_id_; }
+  virtual ConfUiCmd GetType() const = 0;
+  virtual bool SendOver(SharedFD fd) = 0;
+  bool IsUserInput() const;
+
+ protected:
+  std::string session_id_;
+  template <typename... Args>
+  static std::string CreateString(Args&&... args) {
+    return "[" + ArgsToStringWithDelim(",", std::forward<Args>(args)...) + "]";
+  }
+  template <typename... Args>
+  static bool Send_(SharedFD fd, const ConfUiCmd cmd,
+                    const std::string& session_id, Args&&... args) {
+    return packet::WritePayload(fd, confui::ToString(cmd), session_id,
+                                std::forward<Args>(args)...);
+  }
+};
+
+template <ConfUiCmd cmd>
+class ConfUiGenericMessage : public ConfUiMessage {
+ public:
+  ConfUiGenericMessage(const std::string& session_id)
+      : ConfUiMessage{session_id} {}
+  virtual ~ConfUiGenericMessage() = default;
+  std::string ToString() const override {
+    return CreateString(session_id_, confui::ToString(GetType()));
+  }
+  ConfUiCmd GetType() const override { return cmd; }
+  bool SendOver(SharedFD fd) override {
+    return Send_(fd, GetType(), session_id_);
+  }
+};
+
+class ConfUiAckMessage : public ConfUiMessage {
+ public:
+  ConfUiAckMessage(const std::string& session_id, const bool is_success,
+                   const std::string& status)
+      : ConfUiMessage{session_id},
+        is_success_(is_success),
+        status_message_(status) {}
+  virtual ~ConfUiAckMessage() = default;
+  std::string ToString() const override;
+  ConfUiCmd GetType() const override { return ConfUiCmd::kCliAck; }
+  bool SendOver(SharedFD fd) override;
+  bool IsSuccess() const { return is_success_; }
+  std::string GetStatusMessage() const { return status_message_; }
+
+ private:
+  bool is_success_;
+  std::string status_message_;
+};
+
+// the signed user response sent to the guest
+class ConfUiCliResponseMessage : public ConfUiMessage {
+ public:
+  ConfUiCliResponseMessage(const std::string& session_id,
+                           const UserResponse::type& response,
+                           const std::vector<std::uint8_t>& sign = {},
+                           const std::vector<std::uint8_t>& msg = {})
+      : ConfUiMessage(session_id),
+        response_(response),
+        sign_(sign),
+        message_{msg} {}
+  virtual ~ConfUiCliResponseMessage() = default;
+  std::string ToString() const override;
+  ConfUiCmd GetType() const override { return ConfUiCmd::kCliRespond; }
+  auto GetResponse() const { return response_; }
+  auto GetMessage() const { return message_; }
+  auto GetSign() const { return sign_; }
+  bool SendOver(SharedFD fd) override;
+
+ private:
+  UserResponse::type response_;     // plain format
+  std::vector<std::uint8_t> sign_;  // signed format
+  // second argument to pass via resultCB of promptUserConfirmation
+  std::vector<std::uint8_t> message_;
+};
+
+class ConfUiStartMessage : public ConfUiMessage {
+ public:
+  ConfUiStartMessage(const std::string session_id,
+                     const std::string& prompt_text = "",
+                     const std::vector<std::uint8_t>& extra_data = {},
+                     const std::string& locale = "C",
+                     const std::vector<teeui::UIOption> ui_opts = {})
+      : ConfUiMessage(session_id),
+        prompt_text_(prompt_text),
+        extra_data_(extra_data),
+        locale_(locale),
+        ui_opts_(ui_opts) {}
+  virtual ~ConfUiStartMessage() = default;
+  std::string ToString() const override;
+  ConfUiCmd GetType() const override { return ConfUiCmd::kStart; }
+  std::string GetPromptText() const { return prompt_text_; }
+  std::vector<std::uint8_t> GetExtraData() const { return extra_data_; }
+  std::string GetLocale() const { return locale_; }
+  std::vector<teeui::UIOption> GetUiOpts() const { return ui_opts_; }
+  bool SendOver(SharedFD fd) override;
+
+ private:
+  std::string prompt_text_;
+  std::vector<std::uint8_t> extra_data_;
+  std::string locale_;
+  std::vector<teeui::UIOption> ui_opts_;
+
+  std::string UiOptsToString() const;
+};
+
+// this one is for deliverSecureInputEvent() as well as
+// physical-input based implementation
+class ConfUiUserSelectionMessage : public ConfUiMessage {
+ public:
+  ConfUiUserSelectionMessage(const std::string& session_id,
+                             const UserResponse::type& response)
+      : ConfUiMessage(session_id), response_(response) {}
+  virtual ~ConfUiUserSelectionMessage() = default;
+  std::string ToString() const override;
+  ConfUiCmd GetType() const override { return ConfUiCmd::kUserInputEvent; }
+  auto GetResponse() const { return response_; }
+  bool SendOver(SharedFD fd) override;
+
+ private:
+  UserResponse::type response_;
+};
+
+class ConfUiUserTouchMessage : public ConfUiMessage {
+ public:
+  ConfUiUserTouchMessage(const std::string& session_id, const int x,
+                         const int y)
+      : ConfUiMessage(session_id),
+        x_(x),
+        y_(y),
+        response_(UserResponse::kTouchEvent) {}
+  virtual ~ConfUiUserTouchMessage() = default;
+  std::string ToString() const override;
+  ConfUiCmd GetType() const override { return ConfUiCmd::kUserTouchEvent; }
+  auto GetResponse() const { return response_; }
+  bool SendOver(SharedFD fd) override;
+  std::pair<int, int> GetLocation() { return {x_, y_}; }
+
+ private:
+  int x_;
+  int y_;
+  UserResponse::type response_;
+};
+
+using ConfUiAbortMessage = ConfUiGenericMessage<ConfUiCmd::kAbort>;
+using ConfUiStopMessage = ConfUiGenericMessage<ConfUiCmd::kStop>;
+
+}  // end of namespace confui
+}  // end of namespace cuttlefish
diff --git a/common/libs/confui/utils.h b/common/libs/confui/utils.h
index 2e4ac04..1f3814f 100644
--- a/common/libs/confui/utils.h
+++ b/common/libs/confui/utils.h
@@ -15,6 +15,7 @@
 
 #pragma once
 
+#include <algorithm>
 #include <sstream>
 #include <string>
 #include <type_traits>
@@ -35,7 +36,14 @@
 std::string ArgsToStringWithDelim(Delim&& delim, Args&&... args) {
   std::stringstream ss;
   ([&ss, &delim](auto& arg) { ss << arg << delim; }(args), ...);
-  return ss.str();
+  auto result = ss.str();
+  std::string delim_str(delim);
+  if (!result.empty() && !delim_str.empty()) {
+    for (int i = 0; i < delim_str.size(); i++) {
+      result.pop_back();
+    }
+  }
+  return result;
 }
 
 // make t... to a single string with no blank in between
@@ -44,6 +52,11 @@
   return ArgsToStringWithDelim("", std::forward<Args>(args)...);
 }
 
+inline bool IsOnlyDigits(const std::string& src) {
+  return std::all_of(src.begin(), src.end(),
+                     [](int c) -> bool { return std::isdigit(c); });
+}
+
 // note that no () surrounding LOG(level) << "ConfUI:" is crucial
 #define ConfUiLog(LOG_LEVEL) LOG(LOG_LEVEL) << "ConfUI: "
 
diff --git a/common/libs/fs/Android.bp b/common/libs/fs/Android.bp
index c140e0d..d038e0c 100644
--- a/common/libs/fs/Android.bp
+++ b/common/libs/fs/Android.bp
@@ -20,6 +20,7 @@
 cc_library {
     name: "libcuttlefish_fs",
     srcs: [
+        "epoll.cpp",
         "shared_buf.cc",
         "shared_fd.cpp",
         "shared_fd_stream.cpp",
@@ -49,6 +50,7 @@
 cc_library_static {
     name: "libcuttlefish_fs_product",
     srcs: [
+        "epoll.cpp",
         "shared_buf.cc",
         "shared_fd.cpp",
         "shared_fd_stream.cpp",
diff --git a/common/libs/fs/epoll.cpp b/common/libs/fs/epoll.cpp
new file mode 100644
index 0000000..509d3df
--- /dev/null
+++ b/common/libs/fs/epoll.cpp
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#include "common/libs/fs/epoll.h"
+
+#include <sys/epoll.h>
+
+#include <memory>
+#include <optional>
+#include <set>
+#include <shared_mutex>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+Result<Epoll> Epoll::Create() {
+  int fd = epoll_create1(EPOLL_CLOEXEC);
+  if (fd == -1) {
+    return CF_ERRNO("Failed to create epoll");
+  }
+  SharedFD shared{std::shared_ptr<FileInstance>(new FileInstance(fd, 0))};
+  return Epoll(shared);
+}
+
+Epoll::Epoll() = default;
+
+Epoll::Epoll(SharedFD epoll_fd) : epoll_fd_(epoll_fd) {}
+
+Epoll::Epoll(Epoll&& other) {
+  std::unique_lock own_watched(watched_mutex_, std::defer_lock);
+  std::unique_lock own_epoll(epoll_mutex_, std::defer_lock);
+  std::unique_lock other_epoll(other.epoll_mutex_, std::defer_lock);
+  std::unique_lock other_watched(other.watched_mutex_, std::defer_lock);
+  std::lock(own_watched, own_epoll, other_epoll, other_watched);
+
+  epoll_fd_ = std::move(other.epoll_fd_);
+  watched_ = std::move(other.watched_);
+}
+
+Epoll& Epoll::operator=(Epoll&& other) {
+  std::unique_lock own_watched(watched_mutex_, std::defer_lock);
+  std::unique_lock own_epoll(epoll_mutex_, std::defer_lock);
+  std::unique_lock other_epoll(other.epoll_mutex_, std::defer_lock);
+  std::unique_lock other_watched(other.watched_mutex_, std::defer_lock);
+  std::lock(own_watched, own_epoll, other_epoll, other_watched);
+
+  epoll_fd_ = std::move(other.epoll_fd_);
+  watched_ = std::move(other.watched_);
+  return *this;
+}
+
+Result<void> Epoll::Add(SharedFD fd, uint32_t events) {
+  std::unique_lock watched_lock(watched_mutex_, std::defer_lock);
+  std::shared_lock epoll_lock(epoll_mutex_, std::defer_lock);
+  std::lock(watched_lock, epoll_lock);
+  CF_EXPECT(epoll_fd_->IsOpen(), "Empty Epoll instance");
+
+  if (watched_.count(fd) != 0) {
+    return CF_ERRNO("Watched set already contains fd");
+  }
+  epoll_event event;
+  event.events = events;
+  event.data.fd = fd->fd_;
+  int success = epoll_ctl(epoll_fd_->fd_, EPOLL_CTL_ADD, fd->fd_, &event);
+  if (success != 0 && errno == EEXIST) {
+    // We're already tracking this fd, don't drop it from the set.
+    return CF_ERRNO("epoll_ctl: File descriptor was already present");
+  } else if (success != 0) {
+    return CF_ERRNO("epoll_ctl: Add failed");
+  }
+  watched_.insert(fd);
+  return {};
+}
+
+Result<void> Epoll::AddOrModify(SharedFD fd, uint32_t events) {
+  std::unique_lock watched_lock(watched_mutex_, std::defer_lock);
+  std::shared_lock epoll_lock(epoll_mutex_, std::defer_lock);
+  std::lock(watched_lock, epoll_lock);
+  CF_EXPECT(epoll_fd_->IsOpen(), "Empty Epoll instance");
+
+  epoll_event event;
+  event.events = events;
+  event.data.fd = fd->fd_;
+  int operation = watched_.count(fd) == 0 ? EPOLL_CTL_ADD : EPOLL_CTL_MOD;
+  int success = epoll_ctl(epoll_fd_->fd_, operation, fd->fd_, &event);
+  if (success != 0) {
+    std::string operation_str = operation == EPOLL_CTL_ADD ? "add" : "modify";
+    return CF_ERRNO("epoll_ctl: Operation " << operation_str << " failed");
+  }
+  watched_.insert(fd);
+  return {};
+}
+
+Result<void> Epoll::Modify(SharedFD fd, uint32_t events) {
+  std::unique_lock watched_lock(watched_mutex_, std::defer_lock);
+  std::shared_lock epoll_lock(epoll_mutex_, std::defer_lock);
+  std::lock(watched_lock, epoll_lock);
+  CF_EXPECT(epoll_fd_->IsOpen(), "Empty Epoll instance");
+
+  if (watched_.count(fd) == 0) {
+    return CF_ERR("Watched set did not contain fd");
+  }
+  epoll_event event;
+  event.events = events;
+  event.data.fd = fd->fd_;
+  int success = epoll_ctl(epoll_fd_->fd_, EPOLL_CTL_MOD, fd->fd_, &event);
+  if (success != 0) {
+    return CF_ERRNO("epoll_ctl: Modify failed");
+  }
+  return {};
+}
+
+Result<void> Epoll::Delete(SharedFD fd) {
+  std::unique_lock watched_lock(watched_mutex_, std::defer_lock);
+  std::shared_lock epoll_lock(epoll_mutex_, std::defer_lock);
+  std::lock(watched_lock, epoll_lock);
+  CF_EXPECT(epoll_fd_->IsOpen(), "Empty Epoll instance");
+
+  if (watched_.count(fd) == 0) {
+    return CF_ERR("Watched set did not contain fd");
+  }
+  int success = epoll_ctl(epoll_fd_->fd_, EPOLL_CTL_DEL, fd->fd_, nullptr);
+  if (success != 0) {
+    return CF_ERRNO("epoll_ctl: Delete failed");
+  }
+  watched_.erase(fd);
+  return {};
+}
+
+Result<std::optional<EpollEvent>> Epoll::Wait() {
+  epoll_event event;
+  int success;
+  {
+    std::shared_lock lock(epoll_mutex_);
+    CF_EXPECT(epoll_fd_->IsOpen(), "Empty Epoll instance");
+    success = epoll_wait(epoll_fd_->fd_, &event, 1, -1);
+  }
+  if (success == -1) {
+    return CF_ERRNO("epoll_wait failed");
+  } else if (success == 0) {
+    return {};
+  } else if (success != 1) {
+    return CF_ERR("epoll_wait returned an unexpected value");
+  }
+  EpollEvent ret;
+  ret.events = event.events;
+  std::shared_lock lock(watched_mutex_);
+  for (const auto& watched : watched_) {
+    if (watched->fd_ == event.data.fd) {
+      ret.fd = watched;
+      break;
+    }
+  }
+  if (!ret.fd->IsOpen()) {
+    // Couldn't find the matching SharedFD to the file descriptor. We probably
+    // lost the race to lock watched_mutex_ against a delete call. Treat this
+    // as a spurious wakeup.
+    return {};
+  }
+  return ret;
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/fs/epoll.h b/common/libs/fs/epoll.h
new file mode 100644
index 0000000..21bc618
--- /dev/null
+++ b/common/libs/fs/epoll.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/epoll.h>
+
+#include <memory>
+#include <optional>
+#include <set>
+#include <shared_mutex>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+struct EpollEvent {
+  SharedFD fd;
+  uint32_t events;
+};
+
+class Epoll {
+ public:
+  static Result<Epoll> Create();
+  Epoll(); // Invalid instance
+  Epoll(Epoll&&);
+  Epoll& operator=(Epoll&&);
+
+  Result<void> Add(SharedFD fd, uint32_t events);
+  Result<void> Modify(SharedFD fd, uint32_t events);
+  Result<void> AddOrModify(SharedFD fd, uint32_t events);
+  Result<void> Delete(SharedFD fd);
+  Result<std::optional<EpollEvent>> Wait();
+
+ private:
+  Epoll(SharedFD);
+
+  /**
+   * This read-write mutex is read-locked to perform epoll operations, and
+   * write-locked to replace the file descriptor.
+   *
+   * A read-write mutex is used here to make it possible to update the watched
+   * set while the epoll resource is being waited on by another thread, while
+   * excluding the possibility of the move constructor or assignment constructor
+   * from stealing the file descriptor out from under waiting threads.
+   */
+  std::shared_mutex epoll_mutex_;
+  SharedFD epoll_fd_;
+  /**
+   * This read-write mutex is read-locked when interacting with it as a const
+   * std::set, and write-locked when interacting with it as a std::set.
+   */
+  std::shared_mutex watched_mutex_;
+  std::set<SharedFD> watched_;
+};
+
+}  // namespace cuttlefish
diff --git a/common/libs/fs/shared_buf.cc b/common/libs/fs/shared_buf.cc
index 61ff51e..f4b24dd 100644
--- a/common/libs/fs/shared_buf.cc
+++ b/common/libs/fs/shared_buf.cc
@@ -33,32 +33,34 @@
 ssize_t WriteAll(SharedFD fd, const char* buf, size_t size) {
   size_t total_written = 0;
   ssize_t written = 0;
-  while ((written = fd->Write((void*)&(buf[total_written]), size - total_written)) > 0) {
-    if (written < 0) {
-      errno = fd->GetErrno();
-      return written;
+  do {
+    written = fd->Write((void*)&(buf[total_written]), size - total_written);
+    if (written <= 0) {
+      if (written < 0) {
+        errno = fd->GetErrno();
+        return written;
+      }
+      return total_written;
     }
     total_written += written;
-    if (total_written == size) {
-      break;
-    }
-  }
+  } while (total_written < size);
   return total_written;
 }
 
 ssize_t ReadExact(SharedFD fd, char* buf, size_t size) {
   size_t total_read = 0;
   ssize_t read = 0;
-  while ((read = fd->Read((void*)&(buf[total_read]), size - total_read)) > 0) {
-    if (read < 0) {
-      errno = fd->GetErrno();
-      return read;
+  do {
+    read = fd->Read((void*)&(buf[total_read]), size - total_read);
+    if (read <= 0) {
+      if (read < 0) {
+        errno = fd->GetErrno();
+        return read;
+      }
+      return total_read;
     }
     total_read += read;
-    if (total_read == size) {
-      break;
-    }
-  }
+  } while (total_read < size);
   return total_read;
 }
 
diff --git a/common/libs/fs/shared_buf.h b/common/libs/fs/shared_buf.h
index eb63174..f1a02c2 100644
--- a/common/libs/fs/shared_buf.h
+++ b/common/libs/fs/shared_buf.h
@@ -30,6 +30,7 @@
  *
  * If a read error is encountered, returns -1. buf will contain any data read
  * up until that point and errno will be set.
+ *
  */
 ssize_t ReadAll(SharedFD fd, std::string* buf);
 
@@ -40,6 +41,11 @@
  *
  * If a read error is encountered, returns -1. buf will contain any data read
  * up until that point and errno will be set.
+ *
+ * If the size of buf is 0, read(fd, buf, 0) is effectively called, which means
+ * error(s) might be detected. If detected, the return value would be -1.
+ * If not detected, the return value will be 0.
+ *
  */
 ssize_t ReadExact(SharedFD fd, std::string* buf);
 
@@ -50,6 +56,11 @@
  *
  * If a read error is encountered, returns -1. buf will contain any data read
  * up until that point and errno will be set.
+ *
+ * If the size of buf is 0, read(fd, buf, 0) is effectively called, which means
+ * error(s) might be detected. If detected, the return value would be -1.
+ * If not detected, the return value will be 0.
+ *
  */
 ssize_t ReadExact(SharedFD fd, std::vector<char>* buf);
 
@@ -60,6 +71,11 @@
  *
  * If a read error is encountered, returns -1. buf will contain any data read
  * up until that point and errno will be set.
+ *
+ * When the size is 0, read(fd, buf, 0) is effectively called, which means
+ * error(s) might be detected. If detected, the return value would be -1.
+ * If not detected, the return value will be 0.
+ *
  */
 ssize_t ReadExact(SharedFD fd, char* buf, size_t size);
 
@@ -83,6 +99,12 @@
  *
  * If a write error is encountered, returns -1. Some data may have already been
  * written to fd at that point.
+ *
+ * If the size of buf is 0, WriteAll returns 0 with no error set unless
+ * the fd is a regular file. If fd is a regular file, write(fd, buf, 0) is
+ * effectively called. It may detect errors; if detected, errno is set and
+ * -1 is returned. If not detected, 0 is returned with errno unchanged.
+ *
  */
 ssize_t WriteAll(SharedFD fd, const std::string& buf);
 
@@ -93,6 +115,12 @@
  *
  * If a write error is encountered, returns -1. Some data may have already been
  * written to fd at that point.
+ *
+ * If the size of buf is 0, WriteAll returns 0 with no error set unless
+ * the fd is a regular file. If fd is a regular file, write(fd, buf, 0) is
+ * effectively called. It may detect errors; if detected, errno is set and
+ * -1 is returned. If not detected, 0 is returned with errno unchanged.
+ *
  */
 ssize_t WriteAll(SharedFD fd, const std::vector<char>& buf);
 
@@ -103,6 +131,12 @@
  *
  * If a write error is encountered, returns -1. Some data may have already been
  * written to fd at that point.
+ *
+ * If size is 0, WriteAll returns 0 with no error set unless
+ * the fd is a regular file. If fd is a regular file, write(fd, buf, 0) is
+ * effectively called. It may detect errors; if detected, errno is set and
+ * -1 is returned. If not detected, 0 is returned with errno unchanged.
+ *
  */
 ssize_t WriteAll(SharedFD fd, const char* buf, size_t size);
 
@@ -113,6 +147,12 @@
  *
  * If a write error is encountered, returns -1. Some data may have already been
  * written to fd at that point.
+ *
+ * If ever sizeof(T) is 0, WriteAll returns 0 with no error set unless
+ * the fd is a regular file. If fd is a regular file, write(fd, buf, 0) is
+ * effectively called. It may detect errors; if detected, errno is set and
+ * -1 is returned. If not detected, 0 is returned with errno unchanged.
+ *
  */
 template<typename T>
 ssize_t WriteAllBinary(SharedFD fd, const T* binary_data) {
diff --git a/common/libs/fs/shared_fd.cpp b/common/libs/fs/shared_fd.cpp
index b770a38..b89db85 100644
--- a/common/libs/fs/shared_fd.cpp
+++ b/common/libs/fs/shared_fd.cpp
@@ -15,19 +15,24 @@
  */
 #include "common/libs/fs/shared_fd.h"
 
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-#include <sys/syscall.h>
-#include <cstddef>
 #include <errno.h>
 #include <fcntl.h>
 #include <netinet/in.h>
+#include <poll.h>
+#include <sys/file.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
 #include <unistd.h>
+#include <cstddef>
+
 #include <algorithm>
 #include <vector>
 
-#include "android-base/logging.h"
+#include <android-base/logging.h>
+
+#include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_select.h"
 
 // #define ENABLE_GCE_SHARED_FD_LOGGING 1
@@ -83,24 +88,51 @@
 #endif
 }
 
+bool IsRegularFile(const int fd) {
+  struct stat info;
+  if (fstat(fd, &info) < 0) {
+    return false;
+  }
+  return S_ISREG(info.st_mode);
+}
+
+constexpr size_t kPreferredBufferSize = 8192;
+
 }  // namespace
 
 bool FileInstance::CopyFrom(FileInstance& in, size_t length) {
-  std::vector<char> buffer(8192);
+  std::vector<char> buffer(kPreferredBufferSize);
   while (length > 0) {
     ssize_t num_read = in.Read(buffer.data(), std::min(buffer.size(), length));
-    length -= num_read;
     if (num_read <= 0) {
       return false;
     }
-    if (Write(buffer.data(), num_read) != num_read) {
+    length -= num_read;
+
+    ssize_t written = 0;
+    do {
+      auto res = Write(buffer.data(), num_read);
+     if (res <= 0) {
       // The caller will have to log an appropriate message.
-      return false;
-    }
+       return false;
+     }
+     written += res;
+    } while(written < num_read);
   }
   return true;
 }
 
+bool FileInstance::CopyAllFrom(FileInstance& in) {
+  // FileInstance may have been constructed with a non-zero errno_ value because
+  // the errno variable is not zeroed out before.
+  errno_ = 0;
+  in.errno_ = 0;
+  while (CopyFrom(in, kPreferredBufferSize)) {
+  }
+  // Only return false if there was an actual error.
+  return !GetErrno() && !in.GetErrno();
+}
+
 void FileInstance::Close() {
   std::stringstream message;
   if (fd_ == -1) {
@@ -122,6 +154,16 @@
   fd_ = -1;
 }
 
+bool FileInstance::Chmod(mode_t mode) {
+  int original_error = errno;
+  int ret = fchmod(fd_, mode);
+  if (ret != 0) {
+    errno_ = errno;
+  }
+  errno = original_error;
+  return ret == 0;
+}
+
 int FileInstance::ConnectWithTimeout(const struct sockaddr* addr,
                                      socklen_t addrlen,
                                      struct timeval* timeout) {
@@ -134,7 +176,25 @@
     LOG(ERROR) << "Failed to set O_NONBLOCK: " << StrError();
     return -1;
   }
-  Connect(addr, addrlen);  // This will return immediately because of O_NONBLOCK
+
+  auto connect_res = Connect(
+      addr, addrlen);  // This will return immediately because of O_NONBLOCK
+
+  if (connect_res == 0) {  // Immediate success
+    if (Fcntl(F_SETFL, original_flags) == -1) {
+      LOG(ERROR) << "Failed to restore original flags: " << StrError();
+      return -1;
+    }
+    return 0;
+  }
+
+  if (GetErrno() != EAGAIN && GetErrno() != EINPROGRESS) {
+    LOG(DEBUG) << "Immediate connection failure: " << StrError();
+    if (Fcntl(F_SETFL, original_flags) == -1) {
+      LOG(ERROR) << "Failed to restore original flags: " << StrError();
+    }
+    return -1;
+  }
 
   fd_set fdset;
   FD_ZERO(&fdset);
@@ -221,6 +281,24 @@
   return rval;
 }
 
+int SharedFD::Poll(std::vector<PollSharedFd>& fds, int timeout) {
+  return Poll(fds.data(), fds.size(), timeout);
+}
+
+int SharedFD::Poll(PollSharedFd* fds, size_t num_fds, int timeout) {
+  std::vector<pollfd> native_pollfds(num_fds);
+  for (size_t i = 0; i < num_fds; i++) {
+    native_pollfds[i].fd = fds[i].fd->fd_;
+    native_pollfds[i].events = fds[i].events;
+    native_pollfds[i].revents = 0;
+  }
+  int ret = poll(native_pollfds.data(), native_pollfds.size(), timeout);
+  for (size_t i = 0; i < num_fds; i++) {
+    fds[i].revents = native_pollfds[i].revents;
+  }
+  return ret;
+}
+
 static void MakeAddress(const char* name, bool abstract,
                         struct sockaddr_un* dest, socklen_t* len) {
   memset(dest, 0, sizeof(*dest));
@@ -285,6 +363,20 @@
   return std::shared_ptr<FileInstance>(new FileInstance(fd, error_num));
 }
 
+SharedFD SharedFD::MemfdCreateWithData(const std::string& name, const std::string& data, unsigned int flags) {
+  auto memfd = MemfdCreate(name, flags);
+  if (WriteAll(memfd, data) != data.size()) {
+    return ErrorFD(errno);
+  }
+  if (memfd->LSeek(0, SEEK_SET) != 0) {
+    return ErrorFD(memfd->GetErrno());
+  }
+  if (!memfd->Chmod(0700)) {
+    return ErrorFD(memfd->GetErrno());
+  }
+  return memfd;
+}
+
 bool SharedFD::SocketPair(int domain, int type, int protocol,
                           SharedFD* fd0, SharedFD* fd1) {
   int fds[2];
@@ -310,6 +402,31 @@
   return SharedFD::Open(path, O_CREAT|O_WRONLY|O_TRUNC, mode);
 }
 
+int SharedFD::Fchdir(SharedFD shared_fd) {
+  if (!shared_fd.value_) {
+    return -1;
+  }
+  errno = 0;
+  int rval = TEMP_FAILURE_RETRY(fchdir(shared_fd->fd_));
+  shared_fd->errno_ = errno;
+  return rval;
+}
+
+SharedFD SharedFD::Fifo(const std::string& path, mode_t mode) {
+  struct stat st;
+  if (TEMP_FAILURE_RETRY(stat(path.c_str(), &st)) == 0) {
+    if (TEMP_FAILURE_RETRY(remove(path.c_str())) != 0) {
+      return ErrorFD(errno);
+    }
+  }
+
+  int fd = TEMP_FAILURE_RETRY(mkfifo(path.c_str(), mode));
+  if (fd == -1) {
+    return ErrorFD(errno);
+  }
+  return Open(path, mode);
+}
+
 SharedFD SharedFD::Socket(int domain, int socket_type, int protocol) {
   int fd = TEMP_FAILURE_RETRY(socket(domain, socket_type, protocol));
   if (fd == -1) {
@@ -455,12 +572,14 @@
   addr.svm_cid = cid;
   auto casted_addr = reinterpret_cast<sockaddr*>(&addr);
   if (vsock->Bind(casted_addr, sizeof(addr)) == -1) {
-    LOG(ERROR) << "Bind failed (" << vsock->StrError() << ")";
+    LOG(ERROR) << "Port " << port << " Bind failed (" << vsock->StrError()
+               << ")";
     return SharedFD::ErrorFD(vsock->GetErrno());
   }
   if (type == SOCK_STREAM || type == SOCK_SEQPACKET) {
     if (vsock->Listen(4) < 0) {
-      LOG(ERROR) << "Listen failed (" << vsock->StrError() << ")";
+      LOG(ERROR) << "Port" << port << " Listen failed (" << vsock->StrError()
+                 << ")";
       return SharedFD::ErrorFD(vsock->GetErrno());
     }
   }
@@ -511,4 +630,240 @@
   }
 }
 
+/* static */ std::shared_ptr<FileInstance> FileInstance::ClosedInstance() {
+  return std::shared_ptr<FileInstance>(new FileInstance(-1, EBADF));
+}
+
+int FileInstance::Bind(const struct sockaddr* addr, socklen_t addrlen) {
+  errno = 0;
+  int rval = bind(fd_, addr, addrlen);
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::Connect(const struct sockaddr* addr, socklen_t addrlen) {
+  errno = 0;
+  int rval = connect(fd_, addr, addrlen);
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::UNMANAGED_Dup() {
+  errno = 0;
+  int rval = TEMP_FAILURE_RETRY(dup(fd_));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::UNMANAGED_Dup2(int newfd) {
+  errno = 0;
+  int rval = TEMP_FAILURE_RETRY(dup2(fd_, newfd));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::Fcntl(int command, int value) {
+  errno = 0;
+  int rval = TEMP_FAILURE_RETRY(fcntl(fd_, command, value));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::Flock(int operation) {
+  errno = 0;
+  int rval = TEMP_FAILURE_RETRY(flock(fd_, operation));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::GetSockName(struct sockaddr* addr, socklen_t* addrlen) {
+  errno = 0;
+  int rval = TEMP_FAILURE_RETRY(getsockname(fd_, addr, addrlen));
+  if (rval == -1) {
+    errno_ = errno;
+  }
+  return rval;
+}
+
+unsigned int FileInstance::VsockServerPort() {
+  struct sockaddr_vm vm_socket;
+  socklen_t length = sizeof(vm_socket);
+  GetSockName(reinterpret_cast<struct sockaddr*>(&vm_socket), &length);
+  return vm_socket.svm_port;
+}
+
+int FileInstance::Ioctl(int request, void* val) {
+  errno = 0;
+  int rval = TEMP_FAILURE_RETRY(ioctl(fd_, request, val));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::LinkAtCwd(const std::string& path) {
+  std::string name = "/proc/self/fd/";
+  name += std::to_string(fd_);
+  errno = 0;
+  int rval =
+      linkat(-1, name.c_str(), AT_FDCWD, path.c_str(), AT_SYMLINK_FOLLOW);
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::Listen(int backlog) {
+  errno = 0;
+  int rval = listen(fd_, backlog);
+  errno_ = errno;
+  return rval;
+}
+
+off_t FileInstance::LSeek(off_t offset, int whence) {
+  errno = 0;
+  off_t rval = TEMP_FAILURE_RETRY(lseek(fd_, offset, whence));
+  errno_ = errno;
+  return rval;
+}
+
+ssize_t FileInstance::Recv(void* buf, size_t len, int flags) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(recv(fd_, buf, len, flags));
+  errno_ = errno;
+  return rval;
+}
+
+ssize_t FileInstance::RecvMsg(struct msghdr* msg, int flags) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(recvmsg(fd_, msg, flags));
+  errno_ = errno;
+  return rval;
+}
+
+ssize_t FileInstance::Read(void* buf, size_t count) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(read(fd_, buf, count));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::EventfdRead(eventfd_t* value) {
+  errno = 0;
+  auto rval = eventfd_read(fd_, value);
+  errno_ = errno;
+  return rval;
+}
+
+ssize_t FileInstance::Send(const void* buf, size_t len, int flags) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(send(fd_, buf, len, flags));
+  errno_ = errno;
+  return rval;
+}
+
+ssize_t FileInstance::SendMsg(const struct msghdr* msg, int flags) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(sendmsg(fd_, msg, flags));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::Shutdown(int how) {
+  errno = 0;
+  int rval = shutdown(fd_, how);
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::SetSockOpt(int level, int optname, const void* optval,
+                             socklen_t optlen) {
+  errno = 0;
+  int rval = setsockopt(fd_, level, optname, optval, optlen);
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::GetSockOpt(int level, int optname, void* optval,
+                             socklen_t* optlen) {
+  errno = 0;
+  int rval = getsockopt(fd_, level, optname, optval, optlen);
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::SetTerminalRaw() {
+  errno = 0;
+  termios terminal_settings;
+  int rval = tcgetattr(fd_, &terminal_settings);
+  errno_ = errno;
+  if (rval < 0) {
+    return rval;
+  }
+  cfmakeraw(&terminal_settings);
+  rval = tcsetattr(fd_, TCSANOW, &terminal_settings);
+  errno_ = errno;
+  return rval;
+}
+
+std::string FileInstance::StrError() const {
+  errno = 0;
+  return std::string(strerror(errno_));
+}
+
+ScopedMMap FileInstance::MMap(void* addr, size_t length, int prot, int flags,
+                              off_t offset) {
+  errno = 0;
+  auto ptr = mmap(addr, length, prot, flags, fd_, offset);
+  errno_ = errno;
+  return ScopedMMap(ptr, length);
+}
+
+ssize_t FileInstance::Truncate(off_t length) {
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(ftruncate(fd_, length));
+  errno_ = errno;
+  return rval;
+}
+
+ssize_t FileInstance::Write(const void* buf, size_t count) {
+  if (count == 0 && !IsRegular()) {
+    return 0;
+  }
+  errno = 0;
+  ssize_t rval = TEMP_FAILURE_RETRY(write(fd_, buf, count));
+  errno_ = errno;
+  return rval;
+}
+
+int FileInstance::EventfdWrite(eventfd_t value) {
+  errno = 0;
+  int rval = eventfd_write(fd_, value);
+  errno_ = errno;
+  return rval;
+}
+
+bool FileInstance::IsATTY() {
+  errno = 0;
+  int rval = isatty(fd_);
+  errno_ = errno;
+  return rval;
+}
+
+FileInstance::FileInstance(int fd, int in_errno)
+    : fd_(fd), errno_(in_errno), is_regular_file_(IsRegularFile(fd_)) {
+  // Ensure every file descriptor managed by a FileInstance has the CLOEXEC
+  // flag
+  TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD, FD_CLOEXEC));
+  std::stringstream identity;
+  identity << "fd=" << fd << " @" << this;
+  identity_ = identity.str();
+}
+
+FileInstance* FileInstance::Accept(struct sockaddr* addr,
+                                   socklen_t* addrlen) const {
+  int fd = TEMP_FAILURE_RETRY(accept(fd_, addr, addrlen));
+  if (fd == -1) {
+    return new FileInstance(fd, errno);
+  } else {
+    return new FileInstance(fd, 0);
+  }
+}
+
 }  // namespace cuttlefish
diff --git a/common/libs/fs/shared_fd.h b/common/libs/fs/shared_fd.h
index 114d199..d24d987 100644
--- a/common/libs/fs/shared_fd.h
+++ b/common/libs/fs/shared_fd.h
@@ -34,6 +34,7 @@
 
 #include <memory>
 #include <sstream>
+#include <vector>
 
 #include <errno.h>
 #include <fcntl.h>
@@ -66,6 +67,8 @@
  */
 namespace cuttlefish {
 
+struct PollSharedFd;
+class Epoll;
 class FileInstance;
 
 /**
@@ -126,10 +129,15 @@
   // Fcntl or Dup functions.
   static SharedFD Open(const std::string& pathname, int flags, mode_t mode = 0);
   static SharedFD Creat(const std::string& pathname, mode_t mode);
+  static int Fchdir(SharedFD);
+  static SharedFD Fifo(const std::string& pathname, mode_t mode);
   static bool Pipe(SharedFD* fd0, SharedFD* fd1);
   static SharedFD Event(int initval = 0, int flags = 0);
   static SharedFD MemfdCreate(const std::string& name, unsigned int flags = 0);
+  static SharedFD MemfdCreateWithData(const std::string& name, const std::string& data, unsigned int flags = 0);
   static SharedFD Mkstemp(std::string* path);
+  static int Poll(PollSharedFd* fds, size_t num_fds, int timeout);
+  static int Poll(std::vector<PollSharedFd>& fds, int timeout);
   static bool SocketPair(int domain, int type, int protocol, SharedFD* fd0,
                          SharedFD* fd1);
   static SharedFD Socket(int domain, int socket_type, int protocol);
@@ -227,91 +235,51 @@
 class FileInstance {
   // Give SharedFD access to the aliasing constructor.
   friend class SharedFD;
+  friend class Epoll;
 
  public:
   virtual ~FileInstance() { Close(); }
 
   // This can't be a singleton because our shared_ptr's aren't thread safe.
-  static std::shared_ptr<FileInstance> ClosedInstance() {
-    return std::shared_ptr<FileInstance>(new FileInstance(-1, EBADF));
-  }
+  static std::shared_ptr<FileInstance> ClosedInstance();
 
-  int Bind(const struct sockaddr* addr, socklen_t addrlen) {
-    errno = 0;
-    int rval = bind(fd_, addr, addrlen);
-    errno_ = errno;
-    return rval;
-  }
-
-  int Connect(const struct sockaddr* addr, socklen_t addrlen) {
-    errno = 0;
-    int rval = connect(fd_, addr, addrlen);
-    errno_ = errno;
-    return rval;
-  }
-
+  int Bind(const struct sockaddr* addr, socklen_t addrlen);
+  int Connect(const struct sockaddr* addr, socklen_t addrlen);
   int ConnectWithTimeout(const struct sockaddr* addr, socklen_t addrlen,
                          struct timeval* timeout);
-
   void Close();
 
+  bool Chmod(mode_t mode);
+
   // Returns true if the entire input was copied.
   // Otherwise an error will be set either on this file or the input.
   // The non-const reference is needed to avoid binding this to a particular
   // reference type.
   bool CopyFrom(FileInstance& in, size_t length);
+  // Same as CopyFrom, but reads from input until EOF is reached.
+  bool CopyAllFrom(FileInstance& in);
 
-  int UNMANAGED_Dup() {
-    errno = 0;
-    int rval = TEMP_FAILURE_RETRY(dup(fd_));
-    errno_ = errno;
-    return rval;
-  }
+  int UNMANAGED_Dup();
+  int UNMANAGED_Dup2(int newfd);
+  int Fchdir();
+  int Fcntl(int command, int value);
 
-  int UNMANAGED_Dup2(int newfd) {
-    errno = 0;
-    int rval = TEMP_FAILURE_RETRY(dup2(fd_, newfd));
-    errno_ = errno;
-    return rval;
-  }
-
-  int Fcntl(int command, int value) {
-    errno = 0;
-    int rval = TEMP_FAILURE_RETRY(fcntl(fd_, command, value));
-    errno_ = errno;
-    return rval;
-  }
+  int Flock(int operation);
 
   int GetErrno() const { return errno_; }
+  int GetSockName(struct sockaddr* addr, socklen_t* addrlen);
 
-  int GetSockName(struct sockaddr* addr, socklen_t* addrlen) {
-    errno = 0;
-    int rval = TEMP_FAILURE_RETRY(getsockname(fd_, addr, addrlen));
-    if (rval == -1) {
-      errno_ = errno;
-    }
-    return rval;
-  }
+  unsigned int VsockServerPort();
 
-  unsigned int VsockServerPort() {
-    struct sockaddr_vm vm_socket;
-    socklen_t length = sizeof(vm_socket);
-    GetSockName(reinterpret_cast<struct sockaddr*>(&vm_socket), &length);
-    return vm_socket.svm_port;
-  }
-
-  int Ioctl(int request, void* val = nullptr) {
-    errno = 0;
-    int rval = TEMP_FAILURE_RETRY(ioctl(fd_, request, val));
-    errno_ = errno;
-    return rval;
-  }
-
+  int Ioctl(int request, void* val = nullptr);
   bool IsOpen() const { return fd_ != -1; }
 
   // in probably isn't modified, but the API spec doesn't have const.
   bool IsSet(fd_set* in) const;
 
+  // whether this is a regular file or not
+  bool IsRegular() const { return is_regular_file_; }
+
   /**
    * Adds a hard link to a file descriptor, based on the current working
    * directory of the process or to some absolute path.
@@ -321,73 +289,16 @@
    * Using this on a file opened with O_TMPFILE can link it into the filesystem.
    */
   // Used with O_TMPFILE files to attach them to the filesystem.
-  int LinkAtCwd(const std::string& path) {
-    std::string name = "/proc/self/fd/";
-    name += std::to_string(fd_);
-    errno = 0;
-    int rval = linkat(
-        -1, name.c_str(), AT_FDCWD, path.c_str(), AT_SYMLINK_FOLLOW);
-    errno_ = errno;
-    return rval;
-  }
-
-  int Listen(int backlog) {
-    errno = 0;
-    int rval = listen(fd_, backlog);
-    errno_ = errno;
-    return rval;
-  }
-
+  int LinkAtCwd(const std::string& path);
+  int Listen(int backlog);
   static void Log(const char* message);
-
-  off_t LSeek(off_t offset, int whence) {
-    errno = 0;
-    off_t rval = TEMP_FAILURE_RETRY(lseek(fd_, offset, whence));
-    errno_ = errno;
-    return rval;
-  }
-
-  ssize_t Recv(void* buf, size_t len, int flags) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(recv(fd_, buf, len, flags));
-    errno_ = errno;
-    return rval;
-  }
-
-  ssize_t RecvMsg(struct msghdr* msg, int flags) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(recvmsg(fd_, msg, flags));
-    errno_ = errno;
-    return rval;
-  }
-
-  ssize_t Read(void* buf, size_t count) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(read(fd_, buf, count));
-    errno_ = errno;
-    return rval;
-  }
-
-  int EventfdRead(eventfd_t* value) {
-    errno = 0;
-    auto rval = eventfd_read(fd_, value);
-    errno_ = errno;
-    return rval;
-  }
-
-  ssize_t Send(const void* buf, size_t len, int flags) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(send(fd_, buf, len, flags));
-    errno_ = errno;
-    return rval;
-  }
-
-  ssize_t SendMsg(const struct msghdr* msg, int flags) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(sendmsg(fd_, msg, flags));
-    errno_ = errno;
-    return rval;
-  }
+  off_t LSeek(off_t offset, int whence);
+  ssize_t Recv(void* buf, size_t len, int flags);
+  ssize_t RecvMsg(struct msghdr* msg, int flags);
+  ssize_t Read(void* buf, size_t count);
+  int EventfdRead(eventfd_t* value);
+  ssize_t Send(const void* buf, size_t len, int flags);
+  ssize_t SendMsg(const struct msghdr* msg, int flags);
 
   template <typename... Args>
   ssize_t SendFileDescriptors(const void* buf, size_t len, Args&&... sent_fds) {
@@ -399,118 +310,40 @@
     return ret;
   }
 
-  int Shutdown(int how) {
-    errno = 0;
-    int rval = shutdown(fd_, how);
-    errno_ = errno;
-    return rval;
-  }
-
+  int Shutdown(int how);
   void Set(fd_set* dest, int* max_index) const;
-
-  int SetSockOpt(int level, int optname, const void* optval, socklen_t optlen) {
-    errno = 0;
-    int rval = setsockopt(fd_, level, optname, optval, optlen);
-    errno_ = errno;
-    return rval;
-  }
-
-  int GetSockOpt(int level, int optname, void* optval, socklen_t* optlen) {
-    errno = 0;
-    int rval = getsockopt(fd_, level, optname, optval, optlen);
-    errno_ = errno;
-    return rval;
-  }
-
-  int SetTerminalRaw() {
-    errno = 0;
-    termios terminal_settings;
-    int rval = tcgetattr(fd_, &terminal_settings);
-    errno_ = errno;
-    if (rval < 0) {
-      return rval;
-    }
-    cfmakeraw(&terminal_settings);
-    rval = tcsetattr(fd_, TCSANOW, &terminal_settings);
-    errno_ = errno;
-    return rval;
-  }
-
-  const char* StrError() const {
-    errno = 0;
-    FileInstance* s = const_cast<FileInstance*>(this);
-    char* out = strerror_r(errno_, s->strerror_buf_, sizeof(strerror_buf_));
-
-    // From man page:
-    //  strerror_r() returns a pointer to a string containing the error message.
-    //  This may be either a pointer to a string that the function stores in
-    //  buf, or a pointer to some (immutable) static string (in which case buf
-    //  is unused).
-    if (out != s->strerror_buf_) {
-      strncpy(s->strerror_buf_, out, sizeof(strerror_buf_));
-    }
-    return strerror_buf_;
-  }
-
-  ScopedMMap MMap(void* addr, size_t length, int prot, int flags,
-                  off_t offset) {
-    errno = 0;
-    auto ptr = mmap(addr, length, prot, flags, fd_, offset);
-    errno_ = errno;
-    return ScopedMMap(ptr, length);
-  }
-
-  ssize_t Truncate(off_t length) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(ftruncate(fd_, length));
-    errno_ = errno;
-    return rval;
-  }
-
-  ssize_t Write(const void* buf, size_t count) {
-    errno = 0;
-    ssize_t rval = TEMP_FAILURE_RETRY(write(fd_, buf, count));
-    errno_ = errno;
-    return rval;
-  }
-
-  int EventfdWrite(eventfd_t value) {
-    errno = 0;
-    int rval = eventfd_write(fd_, value);
-    errno_ = errno;
-    return rval;
-  }
-
-  bool IsATTY() {
-    errno = 0;
-    int rval = isatty(fd_);
-    errno_ = errno;
-    return rval;
-  }
+  int SetSockOpt(int level, int optname, const void* optval, socklen_t optlen);
+  int GetSockOpt(int level, int optname, void* optval, socklen_t* optlen);
+  int SetTerminalRaw();
+  std::string StrError() const;
+  ScopedMMap MMap(void* addr, size_t length, int prot, int flags, off_t offset);
+  ssize_t Truncate(off_t length);
+  /*
+   * If the file is a regular file and the count is 0, Write() may detect
+   * error(s) by calling write(fd, buf, 0) declared in <unistd.h>. If detected,
+   * it will return -1. If not, 0 will be returned. For non-regular files such
+   * as socket or pipe, write(fd, buf, 0) is not specified. Write(), however,
+   * will do nothing and just return 0.
+   *
+   */
+  ssize_t Write(const void* buf, size_t count);
+  int EventfdWrite(eventfd_t value);
+  bool IsATTY();
 
  private:
-  FileInstance(int fd, int in_errno) : fd_(fd), errno_(in_errno) {
-    // Ensure every file descriptor managed by a FileInstance has the CLOEXEC
-    // flag
-    TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD, FD_CLOEXEC));
-    std::stringstream identity;
-    identity << "fd=" << fd << " @" << this;
-    identity_ = identity.str();
-  }
-
-  FileInstance* Accept(struct sockaddr* addr, socklen_t* addrlen) const {
-    int fd = TEMP_FAILURE_RETRY(accept(fd_, addr, addrlen));
-    if (fd == -1) {
-      return new FileInstance(fd, errno);
-    } else {
-      return new FileInstance(fd, 0);
-    }
-  }
+  FileInstance(int fd, int in_errno);
+  FileInstance* Accept(struct sockaddr* addr, socklen_t* addrlen) const;
 
   int fd_;
   int errno_;
   std::string identity_;
-  char strerror_buf_[160];
+  bool is_regular_file_;
+};
+
+struct PollSharedFd {
+  SharedFD fd;
+  short events;
+  short revents;
 };
 
 /* Methods that need both a fully defined SharedFD and a fully defined
diff --git a/common/libs/net/netlink_client.cpp b/common/libs/net/netlink_client.cpp
index 7b2404f..b245f7e 100644
--- a/common/libs/net/netlink_client.cpp
+++ b/common/libs/net/netlink_client.cpp
@@ -54,9 +54,14 @@
   char buf[4096];
   struct iovec iov = { buf, sizeof(buf) };
   struct sockaddr_nl sa;
-  struct msghdr msg = { &sa, sizeof(sa), &iov, 1, NULL, 0, 0 };
+  struct msghdr msg {};
   struct nlmsghdr *nh;
 
+  msg.msg_name = &sa;
+  msg.msg_namelen = sizeof(sa);
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+
   int result = netlink_fd_->RecvMsg(&msg, 0);
   if (result  < 0) {
     LOG(ERROR) << "Netlink error: " << strerror(errno);
diff --git a/common/libs/security/Android.bp b/common/libs/security/Android.bp
index 4f22951..fe18a0f 100644
--- a/common/libs/security/Android.bp
+++ b/common/libs/security/Android.bp
@@ -21,6 +21,7 @@
     name: "libcuttlefish_security",
     defaults: ["hidl_defaults", "cuttlefish_host"],
     srcs: [
+        "confui_sign.cpp",
         "gatekeeper_channel.cpp",
         "keymaster_channel.cpp",
     ],
diff --git a/common/libs/security/confui_sign.cpp b/common/libs/security/confui_sign.cpp
new file mode 100644
index 0000000..d643585
--- /dev/null
+++ b/common/libs/security/confui_sign.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common/libs/security/confui_sign.h"
+
+#include <android-base/logging.h>
+
+#include "common/libs/fs/shared_buf.h"
+
+namespace cuttlefish {
+bool ConfUiSignerImpl::Send(SharedFD output,
+                            const confui::SignMessageError error,
+                            const std::vector<std::uint8_t>& payload) {
+#define SET_FLAG_AND_RETURN_SEND \
+  sign_status_ |= kIoError;      \
+  return false
+
+  // this looks redudant but makes sure that the byte-length of
+  // the error is guaranteed to be the same when it is received
+  confui::SignRawMessage msg;
+  msg.error_ = error;
+  auto n_written_err = WriteAllBinary(output, &(msg.error_));
+  if (n_written_err != sizeof(msg.error_)) {
+    sign_status_ |= kIoError;
+    SET_FLAG_AND_RETURN_SEND;
+  }
+  decltype(msg.payload_.size()) payload_size = payload.size();
+  auto n_written_payload_size = WriteAllBinary(output, &payload_size);
+  if (n_written_payload_size != sizeof(payload_size)) {
+    SET_FLAG_AND_RETURN_SEND;
+  }
+  const char* buf = reinterpret_cast<const char*>(payload.data());
+  auto n_written_payload = WriteAll(output, buf, payload.size());
+
+  if (n_written_payload != payload.size()) {
+    SET_FLAG_AND_RETURN_SEND;
+  }
+  return true;
+}
+
+std::optional<confui::SignRawMessage> ConfUiSignerImpl::Receive(
+    SharedFD input) {
+  confui::SignRawMessage msg;
+
+  auto n_read = ReadExactBinary(input, &(msg.error_));
+  if (n_read != sizeof(msg.error_)) {
+    sign_status_ |= kIoError;
+  }
+  if (msg.error_ != confui::SignMessageError::kOk) {
+    sign_status_ |= kLogicError;
+  }
+  if (!IsOk()) {
+    return std::nullopt;
+  }
+
+  decltype(msg.payload_.size()) payload_size = 0;
+  n_read = ReadExactBinary(input, &payload_size);
+  if (n_read != sizeof(payload_size)) {
+    sign_status_ |= kIoError;
+    return std::nullopt;
+  }
+
+  std::vector<std::uint8_t> buffer(payload_size);
+  char* buf_data = reinterpret_cast<char*>(buffer.data());
+  n_read = ReadExact(input, buf_data, payload_size);
+  if (n_read != payload_size) {
+    sign_status_ |= kIoError;
+    return std::nullopt;
+  }
+  msg.payload_.swap(buffer);
+  return {msg};
+}
+
+std::optional<confui::SignRawMessage> ConfUiSignSender::Receive() {
+  return impl_.Receive(server_fd_);
+}
+
+bool ConfUiSignSender::Send(const SignMessageError error,
+                            const std::vector<std::uint8_t>& encoded_hmac) {
+  if (!impl_.IsOk()) {
+    return false;
+  }
+  impl_.Send(server_fd_, error, encoded_hmac);
+  return impl_.IsOk();
+}
+
+bool ConfUiSignRequester::Request(const std::vector<std::uint8_t>& message) {
+  impl_.Send(client_fd_, confui::SignMessageError::kOk, message);
+  return impl_.IsOk();
+}
+
+std::optional<confui::SignRawMessage> ConfUiSignRequester::Receive() {
+  if (!impl_.IsOk()) {
+    return std::nullopt;
+  }
+  return impl_.Receive(client_fd_);
+}
+}  // namespace cuttlefish
diff --git a/common/libs/security/confui_sign.h b/common/libs/security/confui_sign.h
new file mode 100644
index 0000000..d85f936
--- /dev/null
+++ b/common/libs/security/confui_sign.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <optional>
+#include <vector>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+namespace confui {
+enum class SignMessageError : std::uint8_t {
+  kOk = 0,
+  kUnknownError = 1,
+};
+
+struct SignRawMessage {
+  SignMessageError error_;
+  std::vector<std::uint8_t> payload_;
+};
+}  // end of namespace confui
+
+class ConfUiSignerImpl {
+  // status and its mask bits
+  using ReceiveError = std::uint8_t;
+  static const ReceiveError kIoError = 1;
+  static const ReceiveError kLogicError = 2;
+
+ public:
+  ConfUiSignerImpl() : sign_status_(0) {}
+
+  bool IsIoError() const { return (kIoError & sign_status_) != 0; }
+
+  bool IsLogicError() const { return (kLogicError & sign_status_) != 0; }
+
+  bool IsOk() const { return !IsIoError() && !IsLogicError(); }
+
+  // set the sign_status_ if there was an error
+  // TODO(kwstephenkim@): use android::base::Result. aosp/1940753
+  bool Send(SharedFD output, const confui::SignMessageError error,
+            const std::vector<std::uint8_t>& payload);
+  std::optional<confui::SignRawMessage> Receive(SharedFD input);
+
+ private:
+  ReceiveError sign_status_;
+};
+
+/*
+ * secure_env will use this in order:
+ *
+ *   Receive() // receive request
+ *   Send() // send signature
+ */
+class ConfUiSignSender {
+  using SignMessageError = confui::SignMessageError;
+
+ public:
+  ConfUiSignSender(SharedFD fd) : server_fd_(fd) {}
+
+  // note that the error is IO error
+  std::optional<confui::SignRawMessage> Receive();
+  bool Send(const SignMessageError error,
+            const std::vector<std::uint8_t>& encoded_hmac);
+
+  bool IsOk() const { return impl_.IsOk(); }
+  bool IsIoError() const { return impl_.IsIoError(); }
+  bool IsLogicError() const { return impl_.IsLogicError(); }
+
+ private:
+  SharedFD server_fd_;
+  ConfUiSignerImpl impl_;
+};
+
+/**
+ * confirmation UI host will use this in this order:
+ *
+ *   Request()
+ *   Receive()
+ */
+class ConfUiSignRequester {
+  using SignMessageError = confui::SignMessageError;
+
+ public:
+  ConfUiSignRequester(SharedFD fd) : client_fd_(fd) {}
+  bool Request(const std::vector<std::uint8_t>& message);
+  std::optional<confui::SignRawMessage> Receive();
+
+ private:
+  SharedFD client_fd_;
+  ConfUiSignerImpl impl_;
+};
+}  // end of namespace cuttlefish
diff --git a/common/libs/security/keymaster_channel.cpp b/common/libs/security/keymaster_channel.cpp
index 7b3ab86..fde5aa5 100644
--- a/common/libs/security/keymaster_channel.cpp
+++ b/common/libs/security/keymaster_channel.cpp
@@ -14,10 +14,17 @@
  * limitations under the License.
  */
 
-#include "keymaster_channel.h"
+#include "common/libs/security/keymaster_channel.h"
+
+#include <cstdlib>
+#include <memory>
+#include <ostream>
+#include <string>
 
 #include <android-base/logging.h>
-#include "keymaster/android_keymaster_utils.h"
+#include <keymaster/android_keymaster_messages.h>
+#include <keymaster/mem.h>
+#include <keymaster/serializable.h>
 
 #include "common/libs/fs/shared_buf.h"
 
@@ -25,7 +32,7 @@
 
 ManagedKeymasterMessage CreateKeymasterMessage(
     AndroidKeymasterCommand command, bool is_response, size_t payload_size) {
-  auto memory = new uint8_t[payload_size + sizeof(keymaster_message)];
+  auto memory = std::malloc(payload_size + sizeof(keymaster_message));
   auto message = reinterpret_cast<keymaster_message*>(memory);
   message->cmd = command;
   message->is_response = is_response;
@@ -37,7 +44,7 @@
   {
     keymaster::Eraser(ptr, sizeof(keymaster_message) + ptr->payload_size);
   }
-  delete reinterpret_cast<uint8_t*>(ptr);
+  std::free(ptr);
 }
 
 KeymasterChannel::KeymasterChannel(SharedFD input, SharedFD output)
diff --git a/common/libs/security/keymaster_channel.h b/common/libs/security/keymaster_channel.h
index 49c3843..eec5a7f 100644
--- a/common/libs/security/keymaster_channel.h
+++ b/common/libs/security/keymaster_channel.h
@@ -16,13 +16,15 @@
 
 #pragma once
 
-#include "keymaster/android_keymaster_messages.h"
-#include "keymaster/serializable.h"
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+
+#include <keymaster/android_keymaster_messages.h>
+#include <keymaster/serializable.h>
 
 #include "common/libs/fs/shared_fd.h"
 
-#include <memory>
-
 namespace keymaster {
 
 /**
@@ -33,8 +35,8 @@
 struct keymaster_message {
     AndroidKeymasterCommand cmd : 31;
     bool is_response : 1;
-    uint32_t payload_size;
-    uint8_t payload[0];
+    std::uint32_t payload_size;
+    std::uint8_t payload[0];
 };
 
 } // namespace keymaster
@@ -61,8 +63,9 @@
  * Allocates memory for a keymaster_message carrying a message of size
  * `payload_size`.
  */
-ManagedKeymasterMessage CreateKeymasterMessage(
-    AndroidKeymasterCommand command, bool is_response, size_t payload_size);
+ManagedKeymasterMessage CreateKeymasterMessage(AndroidKeymasterCommand command,
+                                               bool is_response,
+                                               std::size_t payload_size);
 
 /*
  * Interface for communication channels that synchronously communicate Keymaster
diff --git a/common/libs/time/monotonic_time.h b/common/libs/time/monotonic_time.h
deleted file mode 100644
index 2839001..0000000
--- a/common/libs/time/monotonic_time.h
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <stdint.h>
-#include <time.h>
-
-namespace cuttlefish {
-namespace time {
-
-static const int64_t kNanosecondsPerSecond = 1000000000;
-
-class TimeDifference {
- public:
-  TimeDifference(time_t seconds, long nanoseconds, int64_t scale) :
-      scale_(scale), truncated_(false) {
-    ts_.tv_sec = seconds;
-    ts_.tv_nsec = nanoseconds;
-    if (scale_ == kNanosecondsPerSecond) {
-      truncated_ = true;
-      truncated_ns_ = 0;
-    }
-  }
-
-  TimeDifference(const TimeDifference& in, int64_t scale) :
-      scale_(scale), truncated_(false) {
-    ts_ = in.GetTS();
-    if (scale_ == kNanosecondsPerSecond) {
-      truncated_ = true;
-      truncated_ns_ = 0;
-    } else if ((in.scale_ % scale_) == 0) {
-      truncated_ = true;
-      truncated_ns_ = ts_.tv_nsec;
-    }
-  }
-
-  TimeDifference(const struct timespec& in, int64_t scale) :
-      ts_(in), scale_(scale), truncated_(false) { }
-
-  TimeDifference operator*(const uint32_t factor) {
-    TimeDifference rval = *this;
-    rval.ts_.tv_sec = ts_.tv_sec * factor;
-    // Create temporary variable to hold the multiplied
-    // nanoseconds so that no overflow is possible.
-    // Nanoseconds must be in [0, 10^9) and so all are less
-    // then 2^30. Even multiplied by the largest uint32
-    // this will fit in a 64-bit int without overflow.
-    int64_t tv_nsec = static_cast<int64_t>(ts_.tv_nsec) * factor;
-    rval.ts_.tv_sec += (tv_nsec / kNanosecondsPerSecond);
-    rval.ts_.tv_nsec = tv_nsec % kNanosecondsPerSecond;
-    return rval;
-  }
-
-  TimeDifference operator+(const TimeDifference& other) const {
-    struct timespec ret = ts_;
-    ret.tv_nsec = (ts_.tv_nsec + other.ts_.tv_nsec) % 1000000000;
-    ret.tv_sec = (ts_.tv_sec + other.ts_.tv_sec) +
-                  (ts_.tv_nsec + other.ts_.tv_nsec) / 1000000000;
-    return TimeDifference(ret, scale_ < other.scale_ ? scale_: other.scale_);
-  }
-
-  TimeDifference operator-(const TimeDifference& other) const {
-    struct timespec ret = ts_;
-    // Keeps nanoseconds positive and allow negative numbers only on
-    // seconds.
-    ret.tv_nsec = (1000000000 + ts_.tv_nsec - other.ts_.tv_nsec) % 1000000000;
-    ret.tv_sec = (ts_.tv_sec - other.ts_.tv_sec) -
-                  (ts_.tv_nsec < other.ts_.tv_nsec ? 1 : 0);
-    return TimeDifference(ret, scale_ < other.scale_ ? scale_: other.scale_);
-  }
-
-  bool operator<(const TimeDifference& other) const {
-    return ts_.tv_sec < other.ts_.tv_sec ||
-           (ts_.tv_sec == other.ts_.tv_sec && ts_.tv_nsec < other.ts_.tv_nsec);
-  }
-
-  int64_t count() const {
-    return ts_.tv_sec * (kNanosecondsPerSecond / scale_) + ts_.tv_nsec / scale_;
-  }
-
-  time_t seconds() const {
-    return ts_.tv_sec;
-  }
-
-  long subseconds_in_ns() const {
-    if (!truncated_) {
-      truncated_ns_ = (ts_.tv_nsec / scale_) * scale_;
-      truncated_ = true;
-    }
-    return truncated_ns_;
-  }
-
-  struct timespec GetTS() const {
-    // We can't assume C++11, so avoid extended initializer lists.
-    struct timespec rval = { ts_.tv_sec, subseconds_in_ns()};
-    return rval;
-  }
-
- protected:
-  struct timespec ts_;
-  int64_t scale_;
-  mutable bool truncated_;
-  mutable long truncated_ns_;
-};
-
-class MonotonicTimePoint {
- public:
-  static MonotonicTimePoint Now() {
-    struct timespec ts;
-#ifdef CLOCK_MONOTONIC_RAW
-    // WARNING:
-    // While we do have CLOCK_MONOTONIC_RAW, we can't depend on it until:
-    // - ALL places relying on MonotonicTimePoint are fixed,
-    // - pthread supports pthread_timewait_monotonic.
-    //
-    // This is currently observable as a LEGITIMATE problem while running
-    // pthread_test. DO NOT revert this to CLOCK_MONOTONIC_RAW until test
-    // passes.
-    clock_gettime(CLOCK_MONOTONIC, &ts);
-#else
-    clock_gettime(CLOCK_MONOTONIC, &ts);
-#endif
-    return MonotonicTimePoint(ts);
-  }
-
-  MonotonicTimePoint() {
-    ts_.tv_sec = 0;
-    ts_.tv_nsec = 0;
-  }
-
-  explicit MonotonicTimePoint(const struct timespec& ts) {
-    ts_ = ts;
-  }
-
-  TimeDifference SinceEpoch() const {
-    return TimeDifference(ts_, 1);
-  }
-
-  TimeDifference operator-(const MonotonicTimePoint& other) const {
-    struct timespec rval;
-    rval.tv_sec = ts_.tv_sec - other.ts_.tv_sec;
-    rval.tv_nsec = ts_.tv_nsec - other.ts_.tv_nsec;
-    if (rval.tv_nsec < 0) {
-      --rval.tv_sec;
-      rval.tv_nsec += kNanosecondsPerSecond;
-    }
-    return TimeDifference(rval, 1);
-  }
-
-  MonotonicTimePoint operator+(const TimeDifference& other) const {
-    MonotonicTimePoint rval = *this;
-    rval.ts_.tv_sec += other.seconds();
-    rval.ts_.tv_nsec += other.subseconds_in_ns();
-    if (rval.ts_.tv_nsec >= kNanosecondsPerSecond) {
-      ++rval.ts_.tv_sec;
-      rval.ts_.tv_nsec -= kNanosecondsPerSecond;
-    }
-    return rval;
-  }
-
-  bool operator==(const MonotonicTimePoint& other) const {
-    return (ts_.tv_sec == other.ts_.tv_sec) &&
-        (ts_.tv_nsec == other.ts_.tv_nsec);
-  }
-
-  bool operator!=(const MonotonicTimePoint& other) const {
-    return !(*this == other);
-  }
-
-  bool operator<(const MonotonicTimePoint& other) const {
-    return ((ts_.tv_sec - other.ts_.tv_sec) < 0) ||
-        ((ts_.tv_sec == other.ts_.tv_sec) &&
-         (ts_.tv_nsec < other.ts_.tv_nsec));
-  }
-
-  bool operator>(const MonotonicTimePoint& other) const {
-    return other < *this;
-  }
-
-  bool operator<=(const MonotonicTimePoint& other) const {
-    return !(*this > other);
-  }
-
-  bool operator>=(const MonotonicTimePoint& other) const {
-    return !(*this < other);
-  }
-
-  MonotonicTimePoint& operator+=(const TimeDifference& other) {
-    ts_.tv_sec += other.seconds();
-    ts_.tv_nsec += other.subseconds_in_ns();
-    if (ts_.tv_nsec >= kNanosecondsPerSecond) {
-      ++ts_.tv_sec;
-      ts_.tv_nsec -= kNanosecondsPerSecond;
-    }
-    return *this;
-  }
-
-  MonotonicTimePoint& operator-=(const TimeDifference& other) {
-    ts_.tv_sec -= other.seconds();
-    ts_.tv_nsec -= other.subseconds_in_ns();
-    if (ts_.tv_nsec < 0) {
-      --ts_.tv_sec;
-      ts_.tv_nsec += kNanosecondsPerSecond;
-    }
-    return *this;
-  }
-
-  void ToTimespec(struct timespec* dest) const {
-    *dest = ts_;
-  }
-
- protected:
-  struct timespec ts_;
-};
-
-class Seconds : public TimeDifference {
- public:
-  explicit Seconds(const TimeDifference& difference) :
-      TimeDifference(difference, kNanosecondsPerSecond) { }
-
-  Seconds(int64_t seconds) :
-      TimeDifference(seconds, 0, kNanosecondsPerSecond) { }
-};
-
-class Milliseconds : public TimeDifference {
- public:
-  explicit Milliseconds(const TimeDifference& difference) :
-      TimeDifference(difference, kScale) { }
-
-  Milliseconds(int64_t ms) : TimeDifference(
-      ms / 1000, (ms % 1000) * kScale, kScale) { }
-
- protected:
-  static const int kScale = kNanosecondsPerSecond / 1000;
-};
-
-class Microseconds : public TimeDifference {
- public:
-  explicit Microseconds(const TimeDifference& difference) :
-      TimeDifference(difference, kScale) { }
-
-  Microseconds(int64_t micros) : TimeDifference(
-      micros / 1000000, (micros % 1000000) * kScale, kScale) { }
-
- protected:
-  static const int kScale = kNanosecondsPerSecond / 1000000;
-};
-
-class Nanoseconds : public TimeDifference {
- public:
-  explicit Nanoseconds(const TimeDifference& difference) :
-      TimeDifference(difference, 1) { }
-  Nanoseconds(int64_t ns) : TimeDifference(ns / kNanosecondsPerSecond,
-                                           ns % kNanosecondsPerSecond, 1) { }
-};
-
-}  // namespace time
-}  // namespace cuttlefish
-
-/**
- * Legacy support for microseconds. Use MonotonicTimePoint in new code.
- */
-static const int64_t kSecsToUsecs = static_cast<int64_t>(1000) * 1000;
-
-static inline int64_t get_monotonic_usecs() {
-  return cuttlefish::time::Microseconds(
-      cuttlefish::time::MonotonicTimePoint::Now().SinceEpoch()).count();
-}
diff --git a/common/libs/utils/Android.bp b/common/libs/utils/Android.bp
index 860b684..412bbef 100644
--- a/common/libs/utils/Android.bp
+++ b/common/libs/utils/Android.bp
@@ -23,14 +23,17 @@
         "archive.cpp",
         "subprocess.cpp",
         "environment.cpp",
-        "size_utils.cpp",
+        "flag_parser.cpp",
+        "shared_fd_flag.cpp",
         "files.cpp",
         "users.cpp",
         "network.cpp",
         "base64.cpp",
         "tcp_socket.cpp",
         "tee_logging.cpp",
+        "unix_sockets.cpp",
         "vsock_connection.cpp",
+        "socket2socket_proxy.cpp",
     ],
     shared: {
         shared_libs: [
@@ -53,6 +56,28 @@
     defaults: ["cuttlefish_host"],
 }
 
+cc_test_host {
+    name: "libcuttlefish_utils_test",
+    srcs: [
+        "flag_parser_test.cpp",
+        "unix_sockets_test.cpp",
+    ],
+    static_libs: [
+        "libbase",
+        "libcuttlefish_fs",
+        "libcuttlefish_utils",
+    ],
+    shared_libs: [
+        "libcrypto",
+        "liblog",
+        "libxml2",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+    defaults: ["cuttlefish_host"],
+}
+
 cc_library {
     name: "libvsock_utils",
     srcs: ["vsock_connection.cpp"],
diff --git a/common/libs/utils/archive.cpp b/common/libs/utils/archive.cpp
index 05fbf5f..0610327 100644
--- a/common/libs/utils/archive.cpp
+++ b/common/libs/utils/archive.cpp
@@ -16,7 +16,9 @@
 
 #include "common/libs/utils/archive.h"
 
+#include <ostream>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <android-base/strings.h>
@@ -79,15 +81,15 @@
   bsdtar_cmd.AddParameter(file);
   bsdtar_cmd.AddParameter("-O");
   bsdtar_cmd.AddParameter(path);
-  std::string stdout, stderr;
-  auto ret = RunWithManagedStdio(std::move(bsdtar_cmd), nullptr, &stdout,
-                                 nullptr);
+  std::string stdout_str;
+  auto ret =
+      RunWithManagedStdio(std::move(bsdtar_cmd), nullptr, &stdout_str, nullptr);
   if (ret != 0) {
     LOG(ERROR) << "Could not extract \"" << path << "\" from \"" << file
                << "\" to memory.";
     return "";
   }
-  return stdout;
+  return stdout_str;
 }
 
 } // namespace cuttlefish
diff --git a/common/libs/utils/base64.cpp b/common/libs/utils/base64.cpp
index 1aa09ce..837c486 100644
--- a/common/libs/utils/base64.cpp
+++ b/common/libs/utils/base64.cpp
@@ -16,19 +16,25 @@
 
 #include "common/libs/utils/base64.h"
 
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <vector>
+
 #include <openssl/base64.h>
 
 namespace cuttlefish {
 
-bool EncodeBase64(const void *data, size_t size, std::string *out) {
-  size_t enc_len = 0;
+bool EncodeBase64(const void *data, std::size_t size, std::string *out) {
+  std::size_t enc_len = 0;
   auto len_res = EVP_EncodedLength(&enc_len, size);
   if (!len_res) {
     return false;
   }
   out->resize(enc_len);
-  auto enc_res = EVP_EncodeBlock(reinterpret_cast<uint8_t *>(out->data()),
-                                 reinterpret_cast<const uint8_t *>(data), size);
+  auto enc_res =
+      EVP_EncodeBlock(reinterpret_cast<std::uint8_t *>(out->data()),
+                      reinterpret_cast<const std::uint8_t *>(data), size);
   if (enc_res < 0) {
     return false;
   }
@@ -36,15 +42,15 @@
   return true;
 }
 
-bool DecodeBase64(const std::string &data, std::vector<uint8_t> *buffer) {
-  size_t out_len;
+bool DecodeBase64(const std::string &data, std::vector<std::uint8_t> *buffer) {
+  std::size_t out_len;
   auto len_res = EVP_DecodedLength(&out_len, data.size());
   if (!len_res) {
     return false;
   }
   buffer->resize(out_len);
   return EVP_DecodeBase64(buffer->data(), &out_len, out_len,
-                          reinterpret_cast<const uint8_t *>(data.data()),
+                          reinterpret_cast<const std::uint8_t *>(data.data()),
                           data.size());
 }
 
diff --git a/common/libs/utils/base64.h b/common/libs/utils/base64.h
index dd262c3..c390232 100644
--- a/common/libs/utils/base64.h
+++ b/common/libs/utils/base64.h
@@ -16,14 +16,15 @@
 
 #pragma once
 
-#include <cinttypes>
+#include <cstddef>
+#include <cstdint>
 #include <string>
 #include <vector>
 
 namespace cuttlefish {
 
-bool EncodeBase64(const void* _data, size_t size, std::string* out);
+bool EncodeBase64(const void* _data, std::size_t size, std::string* out);
 
-bool DecodeBase64(const std::string& data, std::vector<uint8_t>* buffer);
+bool DecodeBase64(const std::string& data, std::vector<std::uint8_t>* buffer);
 
 }  // namespace cuttlefish
diff --git a/common/libs/utils/environment.cpp b/common/libs/utils/environment.cpp
index ec27290..b2643fa 100644
--- a/common/libs/utils/environment.cpp
+++ b/common/libs/utils/environment.cpp
@@ -15,14 +15,17 @@
  */
 
 #include "common/libs/utils/environment.h"
-#include "common/libs/utils/files.h"
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <iostream>
+#include <cstdio>
+#include <cstdlib>
+#include <memory>
+#include <ostream>
+#include <string>
 
 #include <android-base/logging.h>
 
+#include "common/libs/utils/files.h"
+
 namespace cuttlefish {
 
 std::string StringFromEnv(const std::string& varname,
diff --git a/common/libs/utils/files.cpp b/common/libs/utils/files.cpp
index ea84b32..c1b1778 100644
--- a/common/libs/utils/files.cpp
+++ b/common/libs/utils/files.cpp
@@ -18,25 +18,40 @@
 
 #include <android-base/logging.h>
 
+#include <dirent.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <libgen.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
 #include <array>
+#include <cerrno>
+#include <chrono>
 #include <climits>
 #include <cstdio>
 #include <cstdlib>
+#include <cstring>
 #include <fstream>
-#include <libgen.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
+#include <ios>
+#include <iosfwd>
+#include <istream>
+#include <memory>
+#include <ostream>
+#include <ratio>
+#include <string>
 #include <vector>
 
+#include <android-base/macros.h>
+
 #include "common/libs/fs/shared_fd.h"
 
 namespace cuttlefish {
 
-bool FileExists(const std::string& path) {
+bool FileExists(const std::string& path, bool follow_symlinks) {
   struct stat st;
-  return stat(path.c_str(), &st) == 0;
+  return (follow_symlinks ? stat : lstat)(path.c_str(), &st) == 0;
 }
 
 bool FileHasContent(const std::string& path) {
@@ -56,9 +71,9 @@
   return ret;
 }
 
-bool DirectoryExists(const std::string& path) {
+bool DirectoryExists(const std::string& path, bool follow_symlinks) {
   struct stat st;
-  if (stat(path.c_str(), &st) == -1) {
+  if ((follow_symlinks ? stat : lstat)(path.c_str(), &st) == -1) {
     return false;
   }
   if ((st.st_mode & S_IFMT) != S_IFDIR) {
@@ -67,6 +82,18 @@
   return true;
 }
 
+Result<void> EnsureDirectoryExists(const std::string& directory_path) {
+  if (!DirectoryExists(directory_path)) {
+    LOG(DEBUG) << "Setting up " << directory_path;
+    if (mkdir(directory_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) <
+            0 &&
+        errno != EEXIST) {
+      return CF_ERRNO("Failed to create dir: \"" << directory_path);
+    }
+  }
+  return {};
+}
+
 bool IsDirectoryEmpty(const std::string& path) {
   auto direc = ::opendir(path.c_str());
   if (!direc) {
@@ -88,6 +115,40 @@
   return true;
 }
 
+bool RecursivelyRemoveDirectory(const std::string& path) {
+  // Copied from libbase TemporaryDir destructor.
+  auto callback = [](const char* child, const struct stat*, int file_type,
+                     struct FTW*) -> int {
+    switch (file_type) {
+      case FTW_D:
+      case FTW_DP:
+      case FTW_DNR:
+        if (rmdir(child) == -1) {
+          PLOG(ERROR) << "rmdir " << child;
+        }
+        break;
+      case FTW_NS:
+      default:
+        if (rmdir(child) != -1) {
+          break;
+        }
+        // FALLTHRU (for gcc, lint, pcc, etc; and following for clang)
+        FALLTHROUGH_INTENDED;
+      case FTW_F:
+      case FTW_SL:
+      case FTW_SLN:
+        if (unlink(child) == -1) {
+          PLOG(ERROR) << "unlink " << child;
+        }
+        break;
+    }
+    return 0;
+  };
+
+  return nftw(path.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) ==
+         0;
+}
+
 std::string AbsolutePath(const std::string& path) {
   if (path.empty()) {
     return {};
@@ -117,6 +178,11 @@
   return st.st_size;
 }
 
+bool MakeFileExecutable(const std::string& path) {
+  LOG(DEBUG) << "Making " << path << " executable";
+  return chmod(path.c_str(), S_IRWXU) == 0;
+}
+
 // TODO(schuffelen): Use std::filesystem::last_write_time when on C++17
 std::chrono::system_clock::time_point FileModificationTime(const std::string& path) {
   struct stat st;
@@ -138,15 +204,18 @@
 }
 
 bool RemoveFile(const std::string& file) {
-  LOG(DEBUG) << "Removing " << file;
+  LOG(DEBUG) << "Removing file " << file;
   return remove(file.c_str()) == 0;
 }
 
-
 std::string ReadFile(const std::string& file) {
   std::string contents;
   std::ifstream in(file, std::ios::in | std::ios::binary);
   in.seekg(0, std::ios::end);
+  if (in.fail()) {
+    // TODO(schuffelen): Return a failing Result instead
+    return "";
+  }
   contents.resize(in.tellg());
   in.seekg(0, std::ios::beg);
   in.read(&contents[0], contents.size());
@@ -156,6 +225,10 @@
 
 std::string CurrentDirectory() {
   char* path = getcwd(nullptr, 0);
+  if (path == nullptr) {
+    PLOG(ERROR) << "`getcwd(nullptr, 0)` failed";
+    return "";
+  }
   std::string ret(path);
   free(path);
   return ret;
@@ -222,4 +295,10 @@
   return ret;
 }
 
+bool FileIsSocket(const std::string& path) {
+  struct stat st;
+  return stat(path.c_str(), &st) == 0 && S_ISSOCK(st.st_mode);
+}
+
+
 }  // namespace cuttlefish
diff --git a/common/libs/utils/files.h b/common/libs/utils/files.h
index ff1c8d3..fbe6b70 100644
--- a/common/libs/utils/files.h
+++ b/common/libs/utils/files.h
@@ -19,20 +19,28 @@
 
 #include <chrono>
 #include <string>
+#include <vector>
+
+#include "common/libs/utils/result.h"
 
 namespace cuttlefish {
-bool FileExists(const std::string& path);
+bool FileExists(const std::string& path, bool follow_symlinks = true);
 bool FileHasContent(const std::string& path);
 std::vector<std::string> DirectoryContents(const std::string& path);
-bool DirectoryExists(const std::string& path);
+bool DirectoryExists(const std::string& path, bool follow_symlinks = true);
+Result<void> EnsureDirectoryExists(const std::string& directory_path);
 bool IsDirectoryEmpty(const std::string& path);
+bool RecursivelyRemoveDirectory(const std::string& path);
 off_t FileSize(const std::string& path);
 bool RemoveFile(const std::string& file);
 bool RenameFile(const std::string& old_name, const std::string& new_name);
 std::string ReadFile(const std::string& file);
+bool MakeFileExecutable(const std::string& path);
 std::chrono::system_clock::time_point FileModificationTime(const std::string& path);
 std::string cpp_dirname(const std::string& str);
 std::string cpp_basename(const std::string& str);
+// Whether a file exists and is a unix socket
+bool FileIsSocket(const std::string& path);
 
 // The returned value may contain .. or . if these are present in the path
 // argument.
diff --git a/common/libs/utils/flag_parser.cpp b/common/libs/utils/flag_parser.cpp
new file mode 100644
index 0000000..76aeced
--- /dev/null
+++ b/common/libs/utils/flag_parser.cpp
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common/libs/utils/flag_parser.h"
+
+#include <cerrno>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+namespace cuttlefish {
+
+std::ostream& operator<<(std::ostream& out, const FlagAlias& alias) {
+  switch (alias.mode) {
+    case FlagAliasMode::kFlagExact:
+      return out << alias.name;
+    case FlagAliasMode::kFlagPrefix:
+      return out << alias.name << "*";
+    case FlagAliasMode::kFlagConsumesFollowing:
+      return out << alias.name << " *";
+    default:
+      LOG(FATAL) << "Unexpected flag alias mode " << (int)alias.mode;
+  }
+  return out;
+}
+
+Flag& Flag::UnvalidatedAlias(const FlagAlias& alias) & {
+  aliases_.push_back(alias);
+  return *this;
+}
+Flag Flag::UnvalidatedAlias(const FlagAlias& alias) && {
+  aliases_.push_back(alias);
+  return *this;
+}
+
+void Flag::ValidateAlias(const FlagAlias& alias) {
+  using android::base::EndsWith;
+  using android::base::StartsWith;
+
+  CHECK(StartsWith(alias.name, "-")) << "Flags should start with \"-\"";
+  if (alias.mode == FlagAliasMode::kFlagPrefix) {
+    CHECK(EndsWith(alias.name, "=")) << "Prefix flags shold end with \"=\"";
+  }
+
+  CHECK(!HasAlias(alias)) << "Duplicate flag alias: " << alias.name;
+  if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) {
+    CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name}))
+        << "Overlapping flag aliases for " << alias.name;
+    CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name}))
+        << "Overlapping flag aliases for " << alias.name;
+  } else if (alias.mode == FlagAliasMode::kFlagExact) {
+    CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name}))
+        << "Overlapping flag aliases for " << alias.name;
+    CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name}))
+        << "Overlapping flag aliases for " << alias.name;
+  } else if (alias.mode == FlagAliasMode::kFlagConsumesArbitrary) {
+    CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name}))
+        << "Overlapping flag aliases for " << alias.name;
+    CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name}))
+        << "Overlapping flag aliases for " << alias.name;
+  }
+}
+
+Flag& Flag::Alias(const FlagAlias& alias) & {
+  ValidateAlias(alias);
+  aliases_.push_back(alias);
+  return *this;
+}
+Flag Flag::Alias(const FlagAlias& alias) && {
+  ValidateAlias(alias);
+  aliases_.push_back(alias);
+  return *this;
+}
+
+Flag& Flag::Help(const std::string& help) & {
+  help_ = help;
+  return *this;
+}
+Flag Flag::Help(const std::string& help) && {
+  help_ = help;
+  return *this;
+}
+
+Flag& Flag::Getter(std::function<std::string()> fn) & {
+  getter_ = std::move(fn);
+  return *this;
+}
+Flag Flag::Getter(std::function<std::string()> fn) && {
+  getter_ = std::move(fn);
+  return *this;
+}
+
+Flag& Flag::Setter(std::function<bool(const FlagMatch&)> fn) & {
+  setter_ = std::move(fn);
+  return *this;
+}
+Flag Flag::Setter(std::function<bool(const FlagMatch&)> fn) && {
+  setter_ = std::move(fn);
+  return *this;
+}
+
+static bool LikelyFlag(const std::string& next_arg) {
+  return android::base::StartsWith(next_arg, "-");
+}
+
+Flag::FlagProcessResult Flag::Process(
+    const std::string& arg, const std::optional<std::string>& next_arg) const {
+  if (!setter_ && aliases_.size() > 0) {
+    LOG(ERROR) << "No setter for flag with alias " << aliases_[0].name;
+    return FlagProcessResult::kFlagError;
+  }
+  for (auto& alias : aliases_) {
+    switch (alias.mode) {
+      case FlagAliasMode::kFlagConsumesArbitrary:
+        if (arg != alias.name) {
+          continue;
+        }
+        if (!next_arg || LikelyFlag(*next_arg)) {
+          if (!(*setter_)({arg, ""})) {
+            LOG(ERROR) << "Processing \"" << arg << "\" failed";
+            return FlagProcessResult::kFlagError;
+          }
+          return FlagProcessResult::kFlagConsumed;
+        }
+        if (!(*setter_)({arg, *next_arg})) {
+          LOG(ERROR) << "Processing \"" << arg << "\" \"" << *next_arg
+                     << "\" failed";
+          return FlagProcessResult::kFlagError;
+        }
+        return FlagProcessResult::kFlagConsumedOnlyFollowing;
+      case FlagAliasMode::kFlagConsumesFollowing:
+        if (arg != alias.name) {
+          continue;
+        }
+        if (!next_arg) {
+          LOG(ERROR) << "Expected an argument after \"" << arg << "\"";
+          return FlagProcessResult::kFlagError;
+        }
+        if (!(*setter_)({arg, *next_arg})) {
+          LOG(ERROR) << "Processing \"" << arg << "\" \"" << *next_arg
+                     << "\" failed";
+          return FlagProcessResult::kFlagError;
+        }
+        return FlagProcessResult::kFlagConsumedWithFollowing;
+      case FlagAliasMode::kFlagExact:
+        if (arg != alias.name) {
+          continue;
+        }
+        if (!(*setter_)({arg, arg})) {
+          LOG(ERROR) << "Processing \"" << arg << "\" failed";
+          return FlagProcessResult::kFlagError;
+        }
+        return FlagProcessResult::kFlagConsumed;
+      case FlagAliasMode::kFlagPrefix:
+        if (!android::base::StartsWith(arg, alias.name)) {
+          continue;
+        }
+        if (!(*setter_)({alias.name, arg.substr(alias.name.size())})) {
+          LOG(ERROR) << "Processing \"" << arg << "\" failed";
+          return FlagProcessResult::kFlagError;
+        }
+        return FlagProcessResult::kFlagConsumed;
+      default:
+        LOG(ERROR) << "Unknown flag alias mode: " << (int)alias.mode;
+        return FlagProcessResult::kFlagError;
+    }
+  }
+  return FlagProcessResult::kFlagSkip;
+}
+
+bool Flag::Parse(std::vector<std::string>& arguments) const {
+  for (int i = 0; i < arguments.size();) {
+    std::string arg = arguments[i];
+    std::optional<std::string> next_arg;
+    if (i < arguments.size() - 1) {
+      next_arg = arguments[i + 1];
+    }
+    auto result = Process(arg, next_arg);
+    if (result == FlagProcessResult::kFlagError) {
+      return false;
+    } else if (result == FlagProcessResult::kFlagConsumed) {
+      arguments.erase(arguments.begin() + i);
+    } else if (result == FlagProcessResult::kFlagConsumedWithFollowing) {
+      arguments.erase(arguments.begin() + i, arguments.begin() + i + 2);
+    } else if (result == FlagProcessResult::kFlagConsumedOnlyFollowing) {
+      arguments.erase(arguments.begin() + i + 1, arguments.begin() + i + 2);
+    } else if (result == FlagProcessResult::kFlagSkip) {
+      i++;
+      continue;
+    } else {
+      LOG(ERROR) << "Unknown FlagProcessResult: " << (int)result;
+      return false;
+    }
+  }
+  return true;
+}
+bool Flag::Parse(std::vector<std::string>&& arguments) const {
+  return Parse(static_cast<std::vector<std::string>&>(arguments));
+}
+
+bool Flag::HasAlias(const FlagAlias& test) const {
+  for (const auto& alias : aliases_) {
+    if (alias.mode == test.mode && alias.name == test.name) {
+      return true;
+    }
+  }
+  return false;
+}
+
+static std::string XmlEscape(const std::string& s) {
+  using android::base::StringReplace;
+  return StringReplace(StringReplace(s, "<", "&lt;", true), ">", "&gt;", true);
+}
+
+bool Flag::WriteGflagsCompatXml(std::ostream& out) const {
+  std::unordered_set<std::string> name_guesses;
+  for (const auto& alias : aliases_) {
+    std::string_view name = alias.name;
+    if (!android::base::ConsumePrefix(&name, "-")) {
+      continue;
+    }
+    android::base::ConsumePrefix(&name, "-");
+    if (alias.mode == FlagAliasMode::kFlagExact) {
+      android::base::ConsumePrefix(&name, "no");
+      name_guesses.insert(std::string{name});
+    } else if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) {
+      name_guesses.insert(std::string{name});
+    } else if (alias.mode == FlagAliasMode::kFlagPrefix) {
+      if (!android::base::ConsumeSuffix(&name, "=")) {
+        continue;
+      }
+      name_guesses.insert(std::string{name});
+    }
+  }
+  bool found_alias = false;
+  for (const auto& name : name_guesses) {
+    bool has_bool_aliases =
+        HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) &&
+        HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) &&
+        HasAlias({FlagAliasMode::kFlagExact, "-" + name}) &&
+        HasAlias({FlagAliasMode::kFlagExact, "--" + name}) &&
+        HasAlias({FlagAliasMode::kFlagExact, "-no" + name}) &&
+        HasAlias({FlagAliasMode::kFlagExact, "--no" + name});
+    bool has_other_aliases =
+        HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) &&
+        HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) &&
+        HasAlias({FlagAliasMode::kFlagConsumesFollowing, "-" + name}) &&
+        HasAlias({FlagAliasMode::kFlagConsumesFollowing, "--" + name});
+    if (has_bool_aliases && has_other_aliases) {
+      LOG(ERROR) << "Expected exactly one of has_bool_aliases and "
+                 << "has_other_aliases, got both for \"" << name << "\".";
+      return false;
+    } else if (!has_bool_aliases && !has_other_aliases) {
+      continue;
+    }
+    found_alias = true;
+    // Lifted from external/gflags/src/gflags_reporting.cc:DescribeOneFlagInXML
+    out << "<flag>\n";
+    out << "  <file>file.cc</file>\n";
+    out << "  <name>" << XmlEscape(name) << "</name>\n";
+    auto help = help_ ? XmlEscape(*help_) : std::string{""};
+    out << "  <meaning>" << help << "</meaning>\n";
+    auto value = getter_ ? XmlEscape((*getter_)()) : std::string{""};
+    out << "  <default>" << value << "</default>\n";
+    out << "  <current>" << value << "</current>\n";
+    out << "  <type>" << (has_bool_aliases ? "bool" : "string") << "</type>\n";
+    out << "</flag>\n";
+  }
+  return found_alias;
+}
+
+std::ostream& operator<<(std::ostream& out, const Flag& flag) {
+  out << "[";
+  for (auto it = flag.aliases_.begin(); it != flag.aliases_.end(); it++) {
+    if (it != flag.aliases_.begin()) {
+      out << ", ";
+    }
+    out << *it;
+  }
+  out << "]\n";
+  if (flag.help_) {
+    out << "(" << *flag.help_ << ")\n";
+  }
+  if (flag.getter_) {
+    out << "(Current value: \"" << (*flag.getter_)() << "\")\n";
+  }
+  return out;
+}
+
+std::vector<std::string> ArgsToVec(int argc, char** argv) {
+  std::vector<std::string> args;
+  for (int i = 0; i < argc; i++) {
+    args.push_back(argv[i]);
+  }
+  return args;
+}
+
+bool ParseFlags(const std::vector<Flag>& flags,
+                std::vector<std::string>& args) {
+  for (const auto& flag : flags) {
+    if (!flag.Parse(args)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ParseFlags(const std::vector<Flag>& flags,
+                std::vector<std::string>&& args) {
+  for (const auto& flag : flags) {
+    if (!flag.Parse(args)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool WriteGflagsCompatXml(const std::vector<Flag>& flags, std::ostream& out) {
+  for (const auto& flag : flags) {
+    if (!flag.WriteGflagsCompatXml(out)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+Flag HelpFlag(const std::vector<Flag>& flags, const std::string& text) {
+  auto setter = [&](FlagMatch) {
+    if (text.size() > 0) {
+      LOG(INFO) << text;
+    }
+    for (const auto& flag : flags) {
+      LOG(INFO) << flag;
+    }
+    return false;
+  };
+  return Flag()
+      .Alias({FlagAliasMode::kFlagExact, "-help"})
+      .Alias({FlagAliasMode::kFlagExact, "--help"})
+      .Setter(setter);
+}
+
+Flag InvalidFlagGuard() {
+  return Flag()
+      .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, "-"})
+      .Help(
+          "This executable only supports the flags in `-help`. Positional "
+          "arguments may be supported.")
+      .Setter([](const FlagMatch& match) {
+        LOG(ERROR) << "Unknown flag " << match.value;
+        return false;
+      });
+}
+
+Flag UnexpectedArgumentGuard() {
+  return Flag()
+      .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, ""})
+      .Help(
+          "This executable only supports the flags in `-help`. Positional "
+          "arguments are not supported.")
+      .Setter([](const FlagMatch& match) {
+        LOG(ERROR) << "Unexpected argument \"" << match.value << "\"";
+        return false;
+      });
+}
+
+Flag GflagsCompatFlag(const std::string& name) {
+  return Flag()
+      .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="})
+      .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="})
+      .Alias({FlagAliasMode::kFlagConsumesFollowing, "-" + name})
+      .Alias({FlagAliasMode::kFlagConsumesFollowing, "--" + name});
+};
+
+Flag GflagsCompatFlag(const std::string& name, std::string& value) {
+  return GflagsCompatFlag(name)
+      .Getter([&value]() { return value; })
+      .Setter([&value](const FlagMatch& match) {
+        value = match.value;
+        return true;
+      });
+}
+
+template <typename T>
+std::optional<T> ParseInteger(const std::string& value) {
+  if (value.size() == 0) {
+    return {};
+  }
+  const char* base = value.c_str();
+  char* end = nullptr;
+  errno = 0;
+  auto r = strtoll(base, &end, /* auto-detect */ 0);
+  if (errno != 0 || end != base + value.size()) {
+    return {};
+  }
+  if (static_cast<T>(r) != r) {
+    return {};
+  }
+  return r;
+}
+
+template <typename T>
+static Flag GflagsCompatNumericFlagGeneric(const std::string& name, T& value) {
+  return GflagsCompatFlag(name)
+      .Getter([&value]() { return std::to_string(value); })
+      .Setter([&value](const FlagMatch& match) {
+        auto parsed = ParseInteger<T>(match.value);
+        if (parsed) {
+          value = *parsed;
+          return true;
+        } else {
+          LOG(ERROR) << "Failed to parse \"" << match.value
+                     << "\" as an integer";
+          return false;
+        }
+      });
+}
+
+Flag GflagsCompatFlag(const std::string& name, int32_t& value) {
+  return GflagsCompatNumericFlagGeneric(name, value);
+}
+
+Flag GflagsCompatFlag(const std::string& name, bool& value) {
+  return Flag()
+      .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="})
+      .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="})
+      .Alias({FlagAliasMode::kFlagExact, "-" + name})
+      .Alias({FlagAliasMode::kFlagExact, "--" + name})
+      .Alias({FlagAliasMode::kFlagExact, "-no" + name})
+      .Alias({FlagAliasMode::kFlagExact, "--no" + name})
+      .Getter([&value]() { return value ? "true" : "false"; })
+      .Setter([name, &value](const FlagMatch& match) {
+        const auto& key = match.key;
+        if (key == "-" + name || key == "--" + name) {
+          value = true;
+          return true;
+        } else if (key == "-no" + name || key == "--no" + name) {
+          value = false;
+          return true;
+        } else if (key == "-" + name + "=" || key == "--" + name + "=") {
+          if (match.value == "true") {
+            value = true;
+            return true;
+          } else if (match.value == "false") {
+            value = false;
+            return true;
+          } else {
+            LOG(ERROR) << "Unexpected boolean value \"" << match.value << "\""
+                       << " for \"" << name << "\"";
+            return false;
+          }
+        }
+        LOG(ERROR) << "Unexpected key \"" << match.key << "\""
+                   << " for \"" << name << "\"";
+        return false;
+      });
+};
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/flag_parser.h b/common/libs/utils/flag_parser.h
new file mode 100644
index 0000000..b45c544
--- /dev/null
+++ b/common/libs/utils/flag_parser.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <vector>
+
+/* Support for parsing individual flags out of a larger list of flags. This
+ * supports externally determining the order that flags are evaluated in, and
+ * incrementally integrating with existing flag parsing implementations.
+ *
+ * Start with Flag() or one of the GflagsCompatFlag(...) functions to create new
+ * flags. These flags should be aggregated through the application through some
+ * other mechanism and then evaluated individually with Flag::Parse or together
+ * with ParseFlags on arguments. */
+
+namespace cuttlefish {
+
+/* The matching behavior used with the name in `FlagAlias::name`. */
+enum class FlagAliasMode {
+  /* Match arguments of the form `<name><value>`. In practice, <name> usually
+   * looks like "-flag=" or "--flag=", where the "-" and "=" are included in
+   * parsing. */
+  kFlagPrefix,
+  /* Match arguments of the form `<name>`. In practice, <name> will look like
+   * "-flag" or "--flag". */
+  kFlagExact,
+  /* Match a pair of arguments of the form `<name>` `<value>`. In practice,
+   * <name> will look like "-flag" or "--flag". */
+  kFlagConsumesFollowing,
+  /* Match a sequence of arguments of the form `<name>` `<value>` `<value>`.
+   * This uses heuristics to try to determine when `<value>` is actually another
+   * flag. */
+  kFlagConsumesArbitrary,
+};
+
+/* A single matching rule for a `Flag`. One `Flag` can have multiple rules. */
+struct FlagAlias {
+  FlagAliasMode mode;
+  std::string name;
+};
+
+std::ostream& operator<<(std::ostream&, const FlagAlias&);
+
+/* A successful match in an argument list from a `FlagAlias` inside a `Flag`.
+ * The `key` value corresponds to `FlagAlias::name`. For a match of
+ * `FlagAliasMode::kFlagExact`, `key` and `value` will both be the `name`. */
+struct FlagMatch {
+  std::string key;
+  std::string value;
+};
+
+class Flag {
+ public:
+  /* Add an alias that triggers matches and calls to the `Setter` function. */
+  Flag& Alias(const FlagAlias& alias) &;
+  Flag Alias(const FlagAlias& alias) &&;
+  /* Set help text, visible in the class ostream writer method. Optional. */
+  Flag& Help(const std::string&) &;
+  Flag Help(const std::string&) &&;
+  /* Set a loader that displays the current value in help text. Optional. */
+  Flag& Getter(std::function<std::string()>) &;
+  Flag Getter(std::function<std::string()>) &&;
+  /* Set the callback for matches. The callback be invoked multiple times. */
+  Flag& Setter(std::function<bool(const FlagMatch&)>) &;
+  Flag Setter(std::function<bool(const FlagMatch&)>) &&;
+
+  /* Examines a list of arguments, removing any matches from the list and
+   * invoking the `Setter` for every match. Returns `false` if the callback ever
+   * returns `false`. Non-matches are left in place. */
+  bool Parse(std::vector<std::string>& flags) const;
+  bool Parse(std::vector<std::string>&& flags) const;
+
+  /* Write gflags `--helpxml` style output for a string-type flag. */
+  bool WriteGflagsCompatXml(std::ostream&) const;
+
+ private:
+  /* Reports whether `Process` wants to consume zero, one, or two arguments. */
+  enum class FlagProcessResult {
+    /* Error in handling a flag, exit flag handling with an error result. */
+    kFlagError,
+    kFlagSkip,                  /* Flag skipped; consume no arguments. */
+    kFlagConsumed,              /* Flag processed; consume one argument. */
+    kFlagConsumedWithFollowing, /* Flag processed; consume 2 arguments. */
+    kFlagConsumedOnlyFollowing, /* Flag processed; consume next argument. */
+  };
+
+  void ValidateAlias(const FlagAlias& alias);
+  Flag& UnvalidatedAlias(const FlagAlias& alias) &;
+  Flag UnvalidatedAlias(const FlagAlias& alias) &&;
+
+  /* Attempt to match a single argument. */
+  FlagProcessResult Process(const std::string& argument,
+                            const std::optional<std::string>& next_arg) const;
+
+  bool HasAlias(const FlagAlias&) const;
+
+  friend std::ostream& operator<<(std::ostream&, const Flag&);
+  friend Flag InvalidFlagGuard();
+  friend Flag UnexpectedArgumentGuard();
+
+  std::vector<FlagAlias> aliases_;
+  std::optional<std::string> help_;
+  std::optional<std::function<std::string()>> getter_;
+  std::optional<std::function<bool(const FlagMatch&)>> setter_;
+};
+
+std::ostream& operator<<(std::ostream&, const Flag&);
+
+std::vector<std::string> ArgsToVec(int argc, char** argv);
+
+/* Handles a list of flags. Flags are matched in the order given in case two
+ * flags match the same argument. Matched flags are removed, leaving only
+ * unmatched arguments. */
+bool ParseFlags(const std::vector<Flag>& flags, std::vector<std::string>& args);
+bool ParseFlags(const std::vector<Flag>& flags, std::vector<std::string>&&);
+
+bool WriteGflagsCompatXml(const std::vector<Flag>&, std::ostream&);
+
+/* If any of these are used, they should be evaluated after all other flags, and
+ * in the order defined here (help before invalid flags, invalid flags before
+ * unexpected arguments). */
+
+/* If a "-help" or "--help" flag is present, prints all the flags and fails. */
+Flag HelpFlag(const std::vector<Flag>& flags, const std::string& text = "");
+/* Catches unrecognized arguments that begin with `-`, and errors out. This
+ * effectively denies unknown flags. */
+Flag InvalidFlagGuard();
+/* Catches any arguments not extracted by other Flag matchers and errors out.
+ * This effectively denies unknown flags and any positional arguments. */
+Flag UnexpectedArgumentGuard();
+
+// Create a flag resembling a gflags argument of the given type. This includes
+// "-[-]flag=*",support for all types, "-[-]noflag" support for booleans, and
+// "-flag *", "--flag *", support for other types. The value passed in the flag
+// is saved to the defined reference.
+Flag GflagsCompatFlag(const std::string& name);
+Flag GflagsCompatFlag(const std::string& name, std::string& value);
+Flag GflagsCompatFlag(const std::string& name, std::int32_t& value);
+Flag GflagsCompatFlag(const std::string& name, bool& value);
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/flag_parser_test.cpp b/common/libs/utils/flag_parser_test.cpp
new file mode 100644
index 0000000..c9798ee
--- /dev/null
+++ b/common/libs/utils/flag_parser_test.cpp
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <common/libs/utils/flag_parser.h>
+
+#include <gtest/gtest.h>
+#include <libxml/tree.h>
+#include <map>
+#include <optional>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace cuttlefish {
+
+TEST(FlagParser, DuplicateAlias) {
+  FlagAlias alias = {FlagAliasMode::kFlagExact, "--flag"};
+  ASSERT_DEATH({ Flag().Alias(alias).Alias(alias); }, "Duplicate flag alias");
+}
+
+TEST(FlagParser, ConflictingAlias) {
+  FlagAlias exact_alias = {FlagAliasMode::kFlagExact, "--flag"};
+  FlagAlias following_alias = {FlagAliasMode::kFlagConsumesFollowing, "--flag"};
+  ASSERT_DEATH({ Flag().Alias(exact_alias).Alias(following_alias); },
+               "Overlapping flag aliases");
+}
+
+TEST(FlagParser, StringFlag) {
+  std::string value;
+  auto flag = GflagsCompatFlag("myflag", value);
+  ASSERT_TRUE(flag.Parse({"-myflag=a"}));
+  ASSERT_EQ(value, "a");
+  ASSERT_TRUE(flag.Parse({"--myflag=b"}));
+  ASSERT_EQ(value, "b");
+  ASSERT_TRUE(flag.Parse({"-myflag", "c"}));
+  ASSERT_EQ(value, "c");
+  ASSERT_TRUE(flag.Parse({"--myflag", "d"}));
+  ASSERT_EQ(value, "d");
+  ASSERT_TRUE(flag.Parse({"--myflag="}));
+  ASSERT_EQ(value, "");
+}
+
+std::optional<std::map<std::string, std::string>> flagXml(const Flag& f) {
+  std::stringstream xml_stream;
+  if (!f.WriteGflagsCompatXml(xml_stream)) {
+    return {};
+  }
+  auto xml = xml_stream.str();
+  // Holds all memory for the parsed structure.
+  std::unique_ptr<xmlDoc, xmlFreeFunc> doc(
+      xmlReadMemory(xml.c_str(), xml.size(), nullptr, nullptr, 0), xmlFree);
+  if (!doc) {
+    return {};
+  }
+  xmlNodePtr root_element = xmlDocGetRootElement(doc.get());
+  std::map<std::string, std::string> elements_map;
+  for (auto elem = root_element->children; elem != nullptr; elem = elem->next) {
+    if (elem->type != xmlElementType::XML_ELEMENT_NODE) {
+      continue;
+    }
+    elements_map[(char*)elem->name] = "";
+    if (elem->children == nullptr) {
+      continue;
+    }
+    if (elem->children->type != XML_TEXT_NODE) {
+      continue;
+    }
+    elements_map[(char*)elem->name] = (char*)elem->children->content;
+  }
+  return elements_map;
+}
+
+TEST(FlagParser, GflagsIncompatibleFlag) {
+  auto flag = Flag().Alias({FlagAliasMode::kFlagExact, "--flag"});
+  ASSERT_FALSE(flagXml(flag));
+}
+
+TEST(FlagParser, StringFlagXml) {
+  std::string value = "somedefault";
+  auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
+  auto xml = flagXml(flag);
+  ASSERT_TRUE(xml);
+  ASSERT_NE((*xml)["file"], "");
+  ASSERT_EQ((*xml)["name"], "myflag");
+  ASSERT_EQ((*xml)["meaning"], "somehelp");
+  ASSERT_EQ((*xml)["default"], "somedefault");
+  ASSERT_EQ((*xml)["current"], "somedefault");
+  ASSERT_EQ((*xml)["type"], "string");
+}
+
+TEST(FlagParser, RepeatedStringFlag) {
+  std::string value;
+  auto flag = GflagsCompatFlag("myflag", value);
+  ASSERT_TRUE(flag.Parse({"-myflag=a", "--myflag", "b"}));
+  ASSERT_EQ(value, "b");
+}
+
+TEST(FlagParser, RepeatedListFlag) {
+  std::vector<std::string> elems;
+  auto flag = GflagsCompatFlag("myflag");
+  flag.Setter([&elems](const FlagMatch& match) {
+    elems.push_back(match.value);
+    return true;
+  });
+  ASSERT_TRUE(flag.Parse({"-myflag=a", "--myflag", "b"}));
+  ASSERT_EQ(elems, (std::vector<std::string>{"a", "b"}));
+}
+
+TEST(FlagParser, FlagRemoval) {
+  std::string value;
+  auto flag = GflagsCompatFlag("myflag", value);
+  std::vector<std::string> flags = {"-myflag=a", "-otherflag=c"};
+  ASSERT_TRUE(flag.Parse(flags));
+  ASSERT_EQ(value, "a");
+  ASSERT_EQ(flags, std::vector<std::string>{"-otherflag=c"});
+  flags = {"-otherflag=a", "-myflag=c"};
+  ASSERT_TRUE(flag.Parse(flags));
+  ASSERT_EQ(value, "c");
+  ASSERT_EQ(flags, std::vector<std::string>{"-otherflag=a"});
+}
+
+TEST(FlagParser, IntFlag) {
+  std::int32_t value = 0;
+  auto flag = GflagsCompatFlag("myflag", value);
+  ASSERT_TRUE(flag.Parse({"-myflag=5"}));
+  ASSERT_EQ(value, 5);
+  ASSERT_TRUE(flag.Parse({"--myflag=6"}));
+  ASSERT_EQ(value, 6);
+  ASSERT_TRUE(flag.Parse({"-myflag", "7"}));
+  ASSERT_EQ(value, 7);
+  ASSERT_TRUE(flag.Parse({"--myflag", "8"}));
+  ASSERT_EQ(value, 8);
+}
+
+TEST(FlagParser, IntFlagXml) {
+  int value = 5;
+  auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
+  auto xml = flagXml(flag);
+  ASSERT_TRUE(xml);
+  ASSERT_NE((*xml)["file"], "");
+  ASSERT_EQ((*xml)["name"], "myflag");
+  ASSERT_EQ((*xml)["meaning"], "somehelp");
+  ASSERT_EQ((*xml)["default"], "5");
+  ASSERT_EQ((*xml)["current"], "5");
+  ASSERT_EQ((*xml)["type"], "string");
+}
+
+TEST(FlagParser, BoolFlag) {
+  bool value = false;
+  auto flag = GflagsCompatFlag("myflag", value);
+  ASSERT_TRUE(flag.Parse({"-myflag"}));
+  ASSERT_TRUE(value);
+
+  value = false;
+  ASSERT_TRUE(flag.Parse({"--myflag"}));
+  ASSERT_TRUE(value);
+
+  value = false;
+  ASSERT_TRUE(flag.Parse({"-myflag=true"}));
+  ASSERT_TRUE(value);
+
+  value = false;
+  ASSERT_TRUE(flag.Parse({"--myflag=true"}));
+  ASSERT_TRUE(value);
+
+  value = true;
+  ASSERT_TRUE(flag.Parse({"-nomyflag"}));
+  ASSERT_FALSE(value);
+
+  value = true;
+  ASSERT_TRUE(flag.Parse({"--nomyflag"}));
+  ASSERT_FALSE(value);
+
+  value = true;
+  ASSERT_TRUE(flag.Parse({"-myflag=false"}));
+  ASSERT_FALSE(value);
+
+  value = true;
+  ASSERT_TRUE(flag.Parse({"--myflag=false"}));
+  ASSERT_FALSE(value);
+
+  ASSERT_FALSE(flag.Parse({"--myflag=nonsense"}));
+}
+
+TEST(FlagParser, BoolFlagXml) {
+  bool value = true;
+  auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
+  auto xml = flagXml(flag);
+  ASSERT_TRUE(xml);
+  ASSERT_NE((*xml)["file"], "");
+  ASSERT_EQ((*xml)["name"], "myflag");
+  ASSERT_EQ((*xml)["meaning"], "somehelp");
+  ASSERT_EQ((*xml)["default"], "true");
+  ASSERT_EQ((*xml)["current"], "true");
+  ASSERT_EQ((*xml)["type"], "bool");
+}
+
+TEST(FlagParser, StringIntFlag) {
+  std::int32_t int_value = 0;
+  std::string string_value;
+  auto int_flag = GflagsCompatFlag("int", int_value);
+  auto string_flag = GflagsCompatFlag("string", string_value);
+  std::vector<Flag> flags = {int_flag, string_flag};
+  ASSERT_TRUE(ParseFlags(flags, {"-int=5", "-string=a"}));
+  ASSERT_EQ(int_value, 5);
+  ASSERT_EQ(string_value, "a");
+  ASSERT_TRUE(ParseFlags(flags, {"--int=6", "--string=b"}));
+  ASSERT_EQ(int_value, 6);
+  ASSERT_EQ(string_value, "b");
+  ASSERT_TRUE(ParseFlags(flags, {"-int", "7", "-string", "c"}));
+  ASSERT_EQ(int_value, 7);
+  ASSERT_EQ(string_value, "c");
+  ASSERT_TRUE(ParseFlags(flags, {"--int", "8", "--string", "d"}));
+  ASSERT_EQ(int_value, 8);
+  ASSERT_EQ(string_value, "d");
+}
+
+TEST(FlagParser, InvalidStringFlag) {
+  std::string value;
+  auto flag = GflagsCompatFlag("myflag", value);
+  ASSERT_FALSE(flag.Parse({"-myflag"}));
+  ASSERT_FALSE(flag.Parse({"--myflag"}));
+}
+
+TEST(FlagParser, InvalidIntFlag) {
+  int value;
+  auto flag = GflagsCompatFlag("myflag", value);
+  ASSERT_FALSE(flag.Parse({"-myflag"}));
+  ASSERT_FALSE(flag.Parse({"--myflag"}));
+  ASSERT_FALSE(flag.Parse({"-myflag=abc"}));
+  ASSERT_FALSE(flag.Parse({"--myflag=def"}));
+  ASSERT_FALSE(flag.Parse({"-myflag", "abc"}));
+  ASSERT_FALSE(flag.Parse({"--myflag", "def"}));
+}
+
+TEST(FlagParser, InvalidFlagGuard) {
+  auto flag = InvalidFlagGuard();
+  ASSERT_TRUE(flag.Parse({}));
+  ASSERT_TRUE(flag.Parse({"positional"}));
+  ASSERT_TRUE(flag.Parse({"positional", "positional2"}));
+  ASSERT_FALSE(flag.Parse({"-flag"}));
+  ASSERT_FALSE(flag.Parse({"-"}));
+}
+
+TEST(FlagParser, UnexpectedArgumentGuard) {
+  auto flag = UnexpectedArgumentGuard();
+  ASSERT_TRUE(flag.Parse({}));
+  ASSERT_FALSE(flag.Parse({"positional"}));
+  ASSERT_FALSE(flag.Parse({"positional", "positional2"}));
+  ASSERT_FALSE(flag.Parse({"-flag"}));
+  ASSERT_FALSE(flag.Parse({"-"}));
+}
+
+class FlagConsumesArbitraryTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    elems_.clear();
+    flag_ = Flag()
+                .Alias({FlagAliasMode::kFlagConsumesArbitrary, "--flag"})
+                .Setter([this](const FlagMatch& match) {
+                  elems_.push_back(match.value);
+                  return true;
+                });
+  }
+  Flag flag_;
+  std::vector<std::string> elems_;
+};
+
+TEST_F(FlagConsumesArbitraryTest, NoValues) {
+  std::vector<std::string> inputs = {"--flag"};
+  ASSERT_TRUE(flag_.Parse(inputs));
+  ASSERT_EQ(inputs, (std::vector<std::string>{}));
+  ASSERT_EQ(elems_, (std::vector<std::string>{""}));
+}
+
+TEST_F(FlagConsumesArbitraryTest, OneValue) {
+  std::vector<std::string> inputs = {"--flag", "value"};
+  ASSERT_TRUE(flag_.Parse(inputs));
+  ASSERT_EQ(inputs, (std::vector<std::string>{}));
+  ASSERT_EQ(elems_, (std::vector<std::string>{"value", ""}));
+}
+
+TEST_F(FlagConsumesArbitraryTest, TwoValues) {
+  std::vector<std::string> inputs = {"--flag", "value1", "value2"};
+  ASSERT_TRUE(flag_.Parse(inputs));
+  ASSERT_EQ(inputs, (std::vector<std::string>{}));
+  ASSERT_EQ(elems_, (std::vector<std::string>{"value1", "value2", ""}));
+}
+
+TEST_F(FlagConsumesArbitraryTest, NoValuesOtherFlag) {
+  std::vector<std::string> inputs = {"--flag", "--otherflag"};
+  ASSERT_TRUE(flag_.Parse(inputs));
+  ASSERT_EQ(inputs, (std::vector<std::string>{"--otherflag"}));
+  ASSERT_EQ(elems_, (std::vector<std::string>{""}));
+}
+
+TEST_F(FlagConsumesArbitraryTest, OneValueOtherFlag) {
+  std::vector<std::string> inputs = {"--flag", "value", "--otherflag"};
+  ASSERT_TRUE(flag_.Parse(inputs));
+  ASSERT_EQ(inputs, (std::vector<std::string>{"--otherflag"}));
+  ASSERT_EQ(elems_, (std::vector<std::string>{"value", ""}));
+}
+
+TEST_F(FlagConsumesArbitraryTest, TwoValuesOtherFlag) {
+  std::vector<std::string> inputs = {"--flag", "v1", "v2", "--otherflag"};
+  ASSERT_TRUE(flag_.Parse(inputs));
+  ASSERT_EQ(inputs, (std::vector<std::string>{"--otherflag"}));
+  ASSERT_EQ(elems_, (std::vector<std::string>{"v1", "v2", ""}));
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/network.cpp b/common/libs/utils/network.cpp
index d1f5f59..9727584 100644
--- a/common/libs/utils/network.cpp
+++ b/common/libs/utils/network.cpp
@@ -16,32 +16,42 @@
 
 #include "common/libs/utils/network.h"
 
-#include <arpa/inet.h>
-#include <linux/if.h>
+// Kernel headers don't mix well with userspace headers, but there is no
+// userspace header that provides the if_tun.h #defines.  Include the kernel
+// header, but move conflicting definitions out of the way using macros.
+#define ethhdr __kernel_ethhdr
 #include <linux/if_tun.h>
+#undef ethhdr
+
+#include <endian.h>
+#include <fcntl.h>
+#include <linux/if_ether.h>
 #include <linux/types.h>
-#include <linux/if_packet.h>
+#include <net/ethernet.h>
+#include <net/if.h>
 #include <netinet/ip.h>
 #include <netinet/udp.h>
-#include <netinet/ether.h>
-#include <string.h>
 
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <ios>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
 #include <android-base/strings.h>
-#include "android-base/logging.h"
 
 #include "common/libs/fs/shared_buf.h"
-#include "common/libs/utils/environment.h"
 #include "common/libs/utils/subprocess.h"
 
 namespace cuttlefish {
 namespace {
 
-static std::string DefaultHostArtifactsPath(const std::string& file_name) {
-  return (StringFromEnv("ANDROID_HOST_OUT", StringFromEnv("HOME", ".")) +
-          "/") +
-         file_name;
-}
-
 // This should be the size of virtio_net_hdr_v1, from linux/virtio_net.h, but
 // the version of that header that ships with android in Pie does not include
 // that struct (it was added in Q).
@@ -96,42 +106,28 @@
     return tap_fd;
   }
 
-  if (HostArch() == Arch::Arm64) {
-    auto tapsetiff_path = DefaultHostArtifactsPath("bin/tapsetiff");
-    Command cmd(tapsetiff_path);
-    cmd.AddParameter(tap_fd);
-    cmd.AddParameter(interface_name.c_str());
-    int ret = cmd.Start().Wait();
-    if (ret != 0) {
-      LOG(ERROR) << "Unable to run tapsetiff.py. Exited with status " << ret;
-      tap_fd->Close();
-      return SharedFD();
-    }
-  } else {
-    struct ifreq ifr;
-    memset(&ifr, 0, sizeof(ifr));
-    ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR;
-    strncpy(ifr.ifr_name, interface_name.c_str(), IFNAMSIZ);
+  struct ifreq ifr;
+  memset(&ifr, 0, sizeof(ifr));
+  ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR;
+  strncpy(ifr.ifr_name, interface_name.c_str(), IFNAMSIZ);
 
-    int err = tap_fd->Ioctl(TUNSETIFF, &ifr);
-    if (err < 0) {
-      LOG(ERROR) << "Unable to connect to " << interface_name
-                 << " tap interface: " << tap_fd->StrError();
-      tap_fd->Close();
-      return SharedFD();
-    }
-
-    // The interface's configuration may have been modified or just not set
-    // correctly on creation. While qemu checks this and enforces the right
-    // configuration, crosvm does not, so it needs to be set before it's passed to
-    // it.
-    tap_fd->Ioctl(TUNSETOFFLOAD,
-                  reinterpret_cast<void*>(TUN_F_CSUM | TUN_F_UFO | TUN_F_TSO4 |
-                                        TUN_F_TSO6));
-    int len = SIZE_OF_VIRTIO_NET_HDR_V1;
-    tap_fd->Ioctl(TUNSETVNETHDRSZ, &len);
+  int err = tap_fd->Ioctl(TUNSETIFF, &ifr);
+  if (err < 0) {
+    LOG(ERROR) << "Unable to connect to " << interface_name
+               << " tap interface: " << tap_fd->StrError();
+    tap_fd->Close();
+    return SharedFD();
   }
 
+  // The interface's configuration may have been modified or just not set
+  // correctly on creation. While qemu checks this and enforces the right
+  // configuration, crosvm does not, so it needs to be set before it's passed to
+  // it.
+  tap_fd->Ioctl(TUNSETOFFLOAD,
+                reinterpret_cast<void*>(TUN_F_CSUM | TUN_F_UFO | TUN_F_TSO4 |
+                                        TUN_F_TSO6));
+  int len = SIZE_OF_VIRTIO_NET_HDR_V1;
+  tap_fd->Ioctl(TUNSETVNETHDRSZ, &len);
   return tap_fd;
 }
 
@@ -139,9 +135,9 @@
   Command cmd("/bin/bash");
   cmd.AddParameter("-c");
   cmd.AddParameter("egrep -h -e \"^iff:.*\" /proc/*/fdinfo/*");
-  std::string stdin, stdout, stderr;
-  RunWithManagedStdio(std::move(cmd), &stdin, &stdout, &stderr);
-  auto lines = android::base::Split(stdout, "\n");
+  std::string stdin_str, stdout_str, stderr_str;
+  RunWithManagedStdio(std::move(cmd), &stdin_str, &stdout_str, &stderr_str);
+  auto lines = android::base::Split(stdout_str, "\n");
   std::set<std::string> tap_interfaces;
   for (const auto& line : lines) {
     if (line == "") {
@@ -310,4 +306,25 @@
   return true;
 }
 
+bool ReleaseDhcpLeases(const std::string& lease_path, SharedFD tap_fd,
+                       const std::uint8_t dhcp_server_ip[4]) {
+  auto lease_file_fd = SharedFD::Open(lease_path, O_RDONLY);
+  if (!lease_file_fd->IsOpen()) {
+    LOG(ERROR) << "Could not open leases file \"" << lease_path << '"';
+    return false;
+  }
+  bool success = true;
+  auto dhcp_leases = ParseDnsmasqLeases(lease_file_fd);
+  for (auto& lease : dhcp_leases) {
+    if (!ReleaseDhcp4(tap_fd, lease.mac_address, lease.ip_address,
+                      dhcp_server_ip)) {
+      LOG(ERROR) << "Failed to release " << lease;
+      success = false;
+    } else {
+      LOG(INFO) << "Successfully dropped " << lease;
+    }
+  }
+  return success;
+}
+
 }  // namespace cuttlefish
diff --git a/common/libs/utils/network.h b/common/libs/utils/network.h
index 64b562e..985476e 100644
--- a/common/libs/utils/network.h
+++ b/common/libs/utils/network.h
@@ -15,7 +15,8 @@
  */
 #pragma once
 
-#include <cstddef>
+#include <cstdint>
+#include <ostream>
 #include <set>
 #include <string>
 #include <vector>
@@ -49,4 +50,6 @@
                   const std::uint8_t ip_address[4],
                   const std::uint8_t dhcp_server_ip[4]);
 
+bool ReleaseDhcpLeases(const std::string& lease_path, SharedFD tap_fd,
+                       const std::uint8_t dhcp_server_ip[4]);
 }
diff --git a/common/libs/utils/result.h b/common/libs/utils/result.h
new file mode 100644
index 0000000..2b732e4
--- /dev/null
+++ b/common/libs/utils/result.h
@@ -0,0 +1,168 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <optional>
+#include <type_traits>
+
+#include <android-base/logging.h>
+#include <android-base/result.h>  // IWYU pragma: export
+
+namespace cuttlefish {
+
+using android::base::Result;
+
+#define CF_ERR_MSG()                             \
+  "  at " << __FILE__ << ":" << __LINE__ << "\n" \
+          << "  in " << __PRETTY_FUNCTION__
+
+/**
+ * Error return macro that includes the location in the file in the error
+ * message. Use CF_ERRNO when including information from errno, otherwise use
+ * the base CF_ERR macro.
+ *
+ * Example usage:
+ *
+ *     if (mkdir(path.c_str()) != 0) {
+ *       return CF_ERRNO("mkdir(\"" << path << "\") failed: "
+ *                       << strerror(errno));
+ *     }
+ *
+ * This will return an error with the text
+ *
+ *     mkdir(...) failed: ...
+ *       at path/to/file.cpp:50
+ *       in Result<std::string> MyFunction()
+ */
+#define CF_ERR(MSG) android::base::Error() << MSG << "\n" << CF_ERR_MSG()
+#define CF_ERRNO(MSG)                        \
+  android::base::ErrnoError() << MSG << "\n" \
+                              << CF_ERR_MSG() << "\n  with errno " << errno
+
+template <typename T>
+T OutcomeDereference(std::optional<T>&& value) {
+  return std::move(*value);
+}
+
+template <typename T>
+typename std::conditional_t<std::is_void_v<T>, bool, T> OutcomeDereference(
+    Result<T>&& result) {
+  if constexpr (std::is_void<T>::value) {
+    return result.ok();
+  } else {
+    return std::move(*result);
+  }
+}
+
+template <typename T>
+typename std::enable_if<std::is_convertible_v<T, bool>, T>::type
+OutcomeDereference(T&& value) {
+  return std::forward<T>(value);
+}
+
+inline bool TypeIsSuccess(bool value) { return value; }
+
+template <typename T>
+bool TypeIsSuccess(std::optional<T>& value) {
+  return value.has_value();
+}
+
+template <typename T>
+bool TypeIsSuccess(Result<T>& value) {
+  return value.ok();
+}
+
+template <typename T>
+bool TypeIsSuccess(Result<T>&& value) {
+  return value.ok();
+}
+
+inline auto ErrorFromType(bool) {
+  return (android::base::Error() << "Received `false`").str();
+}
+
+template <typename T>
+inline auto ErrorFromType(std::optional<T>) {
+  return (android::base::Error() << "Received empty optional").str();
+}
+
+template <typename T>
+auto ErrorFromType(Result<T>& value) {
+  return value.error();
+}
+
+template <typename T>
+auto ErrorFromType(Result<T>&& value) {
+  return value.error();
+}
+
+#define CF_EXPECT_OVERLOAD(_1, _2, NAME, ...) NAME
+
+#define CF_EXPECT2(RESULT, MSG)                                  \
+  ({                                                             \
+    decltype(RESULT)&& macro_intermediate_result = RESULT;       \
+    if (!TypeIsSuccess(macro_intermediate_result)) {             \
+      return android::base::Error()                              \
+             << ErrorFromType(macro_intermediate_result) << "\n" \
+             << MSG << "\n"                                      \
+             << CF_ERR_MSG() << "\n"                             \
+             << "  for CF_EXPECT(" << #RESULT << ")";            \
+    };                                                           \
+    OutcomeDereference(std::move(macro_intermediate_result));    \
+  })
+
+#define CF_EXPECT1(RESULT) CF_EXPECT2(RESULT, "Received error")
+
+/**
+ * Error propagation macro that can be used as an expression.
+ *
+ * The first argument can be either a Result or a type that is convertible to
+ * a boolean. A successful result will return the value inside the result, or
+ * a conversion to a `true` value will return the unconverted value. This is
+ * useful for e.g. pointers which can be tested through boolean conversion.
+ *
+ * In the failure case, this macro will return from the containing function
+ * with a failing Result. The failing result will include information about the
+ * call site, details from the optional second argument if given, and details
+ * from the failing inner expression if it is a Result.
+ *
+ * This macro must be invoked only in functions that return a Result.
+ *
+ * Example usage:
+ *
+ *     Result<std::string> CreateTempDir();
+ *
+ *     Result<std::string> CreatePopulatedTempDir() {
+ *       std::string dir = CF_EXPECT(CreateTempDir(), "Failed to create dir");
+ *       // Do something with dir
+ *       return dir;
+ *     }
+ *
+ * If CreateTempDir fails, the function will returna Result with an error
+ * message that looks like
+ *
+ *      Internal error
+ *        at /path/to/otherfile.cpp:50
+ *        in Result<std::string> CreateTempDir()
+ *      Failed to create dir
+ *        at /path/to/file.cpp:81:
+ *        in Result<std::string> CreatePopulatedTempDir()
+ *        for CF_EXPECT(CreateTempDir())
+ */
+#define CF_EXPECT(...) \
+  CF_EXPECT_OVERLOAD(__VA_ARGS__, CF_EXPECT2, CF_EXPECT1)(__VA_ARGS__)
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/shared_fd_flag.cpp b/common/libs/utils/shared_fd_flag.cpp
new file mode 100644
index 0000000..b894147
--- /dev/null
+++ b/common/libs/utils/shared_fd_flag.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "common/libs/utils/shared_fd_flag.h"
+
+#include <unistd.h>
+
+#include <functional>
+#include <limits>
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/flag_parser.h"
+
+namespace cuttlefish {
+
+static bool Set(const FlagMatch& match, SharedFD& out) {
+  int raw_fd;
+  if (!android::base::ParseInt(match.value.c_str(), &raw_fd)) {
+    LOG(ERROR) << "Failed to parse value \"" << match.value
+               << "\" for fd flag \"" << match.key << "\"";
+    return false;
+  }
+  out = SharedFD::Dup(raw_fd);
+  if (out->IsOpen()) {
+    close(raw_fd);
+  }
+  return true;
+}
+
+Flag SharedFDFlag(SharedFD& out) {
+  return Flag().Setter([&](const FlagMatch& mat) { return Set(mat, out); });
+}
+Flag SharedFDFlag(const std::string& name, SharedFD& out) {
+  return GflagsCompatFlag(name).Setter(
+      [&out](const FlagMatch& mat) { return Set(mat, out); });
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/common/libs/utils/shared_fd_flag.h
similarity index 69%
rename from common/libs/utils/size_utils.cpp
rename to common/libs/utils/shared_fd_flag.h
index 9f25445..33b1555 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/common/libs/utils/shared_fd_flag.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,16 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#pragma once
 
-#include "common/libs/utils/size_utils.h"
+#include <string>
 
-#include <unistd.h>
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/flag_parser.h"
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+Flag SharedFDFlag(SharedFD& out);
+Flag SharedFDFlag(const std::string& name, SharedFD& out);
 
 }  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.h b/common/libs/utils/size_utils.h
index 625cdd8..71bfea6 100644
--- a/common/libs/utils/size_utils.h
+++ b/common/libs/utils/size_utils.h
@@ -26,6 +26,9 @@
 constexpr int PARTITION_SIZE_SHIFT = 12;
 
 // Returns the smallest multiple of 2^align_log greater than or equal to val.
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log);
+constexpr uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
+  uint64_t align = 1ULL << align_log;
+  return ((val + (align - 1)) / align) * align;
+}
 
 }  // namespace cuttlefish
diff --git a/common/libs/utils/socket2socket_proxy.cpp b/common/libs/utils/socket2socket_proxy.cpp
new file mode 100644
index 0000000..e3d7343
--- /dev/null
+++ b/common/libs/utils/socket2socket_proxy.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+#include "common/libs/utils/socket2socket_proxy.h"
+
+#include <sys/socket.h>
+
+#include <cstring>
+#include <functional>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <thread>
+
+#include <android-base/logging.h>
+
+namespace cuttlefish {
+namespace {
+
+void Forward(const std::string& label, SharedFD from, SharedFD to) {
+  auto success = to->CopyAllFrom(*from);
+  if (!success) {
+    if (from->GetErrno()) {
+      LOG(ERROR) << label << ": Error reading: " << from->StrError();
+    }
+    if (to->GetErrno()) {
+      LOG(ERROR) << label << ": Error writing: " << to->StrError();
+    }
+  }
+  to->Shutdown(SHUT_WR);
+  LOG(DEBUG) << label << " completed";
+}
+
+void SetupProxying(SharedFD client, SharedFD target) {
+  std::thread([client, target]() {
+    std::thread client2target(Forward, "client2target", client, target);
+    Forward("target2client", target, client);
+    client2target.join();
+    // The actual proxying is handled in a detached thread so that this function
+    // returns immediately
+  }).detach();
+}
+
+}  // namespace
+
+void Proxy(SharedFD server, std::function<SharedFD()> conn_factory) {
+  while (server->IsOpen()) {
+    auto client = SharedFD::Accept(*server);
+    if (!client->IsOpen()) {
+      LOG(ERROR) << "Failed to accept connection in server: "
+                 << client->StrError();
+      continue;
+    }
+    auto target = conn_factory();
+    if (target->IsOpen()) {
+      SetupProxying(client, target);
+    }
+    // The client will close when it goes out of scope here if the target didn't
+    // open.
+  }
+  LOG(INFO) << "Server closed: " << server->StrError();
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/socket2socket_proxy.h b/common/libs/utils/socket2socket_proxy.h
new file mode 100644
index 0000000..28e17b1
--- /dev/null
+++ b/common/libs/utils/socket2socket_proxy.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+// Executes a TCP proxy
+// Accept() is called on the server in a loop, for every client connection a
+// target connection is created through the conn_factory callback and data is
+// forwarded between the two connections.
+// This function is meant to execute forever, but will return if the server is
+// closed in another thread. It's recommended the caller disables the default
+// behavior for SIGPIPE before calling this function, otherwise it runs the risk
+// or crashing the process when a connection breaks.
+void Proxy(SharedFD server, std::function<SharedFD()> conn_factory);
+}  // namespace cuttlefish
diff --git a/common/libs/utils/subprocess.cpp b/common/libs/utils/subprocess.cpp
index 7c05219..16a7989 100644
--- a/common/libs/utils/subprocess.cpp
+++ b/common/libs/utils/subprocess.cpp
@@ -16,7 +16,7 @@
 
 #include "common/libs/utils/subprocess.h"
 
-#include <errno.h>
+#include <fcntl.h>
 #include <signal.h>
 #include <stdlib.h>
 #include <sys/prctl.h>
@@ -24,13 +24,25 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <cerrno>
+#include <cstring>
 #include <map>
+#include <memory>
+#include <ostream>
 #include <set>
+#include <sstream>
+#include <string>
 #include <thread>
+#include <utility>
+#include <vector>
 
 #include <android-base/logging.h>
+#include <android-base/strings.h>
 
 #include "common/libs/fs/shared_buf.h"
+#include "common/libs/utils/files.h"
+
+extern char** environ;
 
 namespace cuttlefish {
 namespace {
@@ -74,14 +86,35 @@
   ret.push_back(NULL);
   return ret;
 }
-
-void UnsetEnvironment(const std::unordered_set<std::string>& unenv) {
-  for (auto it = unenv.cbegin(); it != unenv.cend(); ++it) {
-    unsetenv(it->c_str());
-  }
-}
 }  // namespace
 
+SubprocessOptions& SubprocessOptions::Verbose(bool verbose) & {
+  verbose_ = verbose;
+  return *this;
+}
+SubprocessOptions SubprocessOptions::Verbose(bool verbose) && {
+  verbose_ = verbose;
+  return *this;
+}
+
+SubprocessOptions& SubprocessOptions::ExitWithParent(bool v) & {
+  exit_with_parent_ = v;
+  return *this;
+}
+SubprocessOptions SubprocessOptions::ExitWithParent(bool v) && {
+  exit_with_parent_ = v;
+  return *this;
+}
+
+SubprocessOptions& SubprocessOptions::InGroup(bool in_group) & {
+  in_group_ = in_group;
+  return *this;
+}
+SubprocessOptions SubprocessOptions::InGroup(bool in_group) && {
+  in_group_ = in_group;
+  return *this;
+}
+
 Subprocess::Subprocess(Subprocess&& subprocess)
     : pid_(subprocess.pid_),
       started_(subprocess.started_),
@@ -110,7 +143,7 @@
   }
   int wstatus = 0;
   auto pid = pid_;  // Wait will set pid_ to -1 after waiting
-  auto wait_ret = Wait(&wstatus, 0);
+  auto wait_ret = waitpid(pid, &wstatus, 0);
   if (wait_ret < 0) {
     auto error = errno;
     LOG(ERROR) << "Error on call to waitpid: " << strerror(error);
@@ -120,7 +153,7 @@
   if (WIFEXITED(wstatus)) {
     retval = WEXITSTATUS(wstatus);
     if (retval) {
-      LOG(ERROR) << "Subprocess " << pid
+      LOG(DEBUG) << "Subprocess " << pid
                  << " exited with error code: " << retval;
     }
   } else if (WIFSIGNALED(wstatus)) {
@@ -130,20 +163,26 @@
   }
   return retval;
 }
-pid_t Subprocess::Wait(int* wstatus, int options) {
+int Subprocess::Wait(siginfo_t* infop, int options) {
   if (pid_ < 0) {
     LOG(ERROR)
         << "Attempt to wait on invalid pid(has it been waited on already?): "
         << pid_;
     return -1;
   }
-  auto retval = waitpid(pid_, wstatus, options);
+  *infop = {};
+  auto retval = waitid(P_PID, pid_, infop, options);
   // We don't want to wait twice for the same process
-  pid_ = -1;
+  bool exited = infop->si_code == CLD_EXITED || infop->si_code == CLD_DUMPED ||
+                infop->si_code == CLD_DUMPED;
+  bool reaped = !(options & WNOWAIT);
+  if (exited && reaped) {
+    pid_ = -1;
+  }
   return retval;
 }
 
-bool KillSubprocess(Subprocess* subprocess) {
+StopperResult KillSubprocess(Subprocess* subprocess) {
   auto pid = subprocess->pid();
   if (pid > 0) {
     auto pgid = getpgid(pid);
@@ -155,13 +194,23 @@
       // to the process and not a (non-existent) group
     }
     bool is_group_head = pid == pgid;
-    if (is_group_head) {
-      return killpg(pid, SIGKILL) == 0;
-    } else {
-      return kill(pid, SIGKILL) == 0;
+    auto kill_ret = (is_group_head ? killpg : kill)(pid, SIGKILL);
+    if (kill_ret == 0) {
+      return StopperResult::kStopSuccess;
     }
+    auto kill_cmd = is_group_head ? "killpg(" : "kill(";
+    PLOG(ERROR) << kill_cmd << pid << ", SIGKILL) failed: ";
+    return StopperResult::kStopFailure;
   }
-  return true;
+  return StopperResult::kStopSuccess;
+}
+
+Command::Command(const std::string& executable, SubprocessStopper stopper)
+    : subprocess_stopper_(stopper) {
+  for (char** env = environ; *env; env++) {
+    env_.emplace_back(*env);
+  }
+  command_.push_back(executable);
 }
 
 Command::~Command() {
@@ -175,45 +224,68 @@
   }
 }
 
-bool Command::BuildParameter(std::stringstream* stream, SharedFD shared_fd) {
+void Command::BuildParameter(std::stringstream* stream, SharedFD shared_fd) {
   int fd;
   if (inherited_fds_.count(shared_fd)) {
     fd = inherited_fds_[shared_fd];
   } else {
     fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
-    if (fd < 0) {
-      LOG(ERROR) << "Could not acquire a new file descriptor: " << shared_fd->StrError();
-      return false;
-    }
+    CHECK(fd >= 0) << "Could not acquire a new file descriptor: "
+                   << shared_fd->StrError();
     inherited_fds_[shared_fd] = fd;
   }
   *stream << fd;
-  return true;
 }
 
-bool Command::RedirectStdIO(Subprocess::StdIOChannel channel,
-                            SharedFD shared_fd) {
-  if (!shared_fd->IsOpen()) {
-    return false;
-  }
-  if (redirects_.count(channel)) {
-    LOG(ERROR) << "Attempted multiple redirections of fd: "
-               << static_cast<int>(channel);
-    return false;
-  }
+Command& Command::RedirectStdIO(Subprocess::StdIOChannel channel,
+                                SharedFD shared_fd) & {
+  CHECK(shared_fd->IsOpen());
+  CHECK(redirects_.count(channel) == 0)
+      << "Attempted multiple redirections of fd: " << static_cast<int>(channel);
   auto dup_fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
-  if (dup_fd < 0) {
-    LOG(ERROR) << "Could not acquire a new file descriptor: " << shared_fd->StrError();
-    return false;
-  }
+  CHECK(dup_fd >= 0) << "Could not acquire a new file descriptor: "
+                     << shared_fd->StrError();
   redirects_[channel] = dup_fd;
-  return true;
+  return *this;
 }
-bool Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
-                            Subprocess::StdIOChannel parent_channel) {
+Command Command::RedirectStdIO(Subprocess::StdIOChannel channel,
+                               SharedFD shared_fd) && {
+  RedirectStdIO(channel, shared_fd);
+  return std::move(*this);
+}
+Command& Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
+                                Subprocess::StdIOChannel parent_channel) & {
   return RedirectStdIO(subprocess_channel,
                        SharedFD::Dup(static_cast<int>(parent_channel)));
 }
+Command Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
+                               Subprocess::StdIOChannel parent_channel) && {
+  RedirectStdIO(subprocess_channel, parent_channel);
+  return std::move(*this);
+}
+
+Command& Command::SetWorkingDirectory(std::string path) & {
+  auto fd = SharedFD::Open(path, O_RDONLY | O_PATH | O_DIRECTORY);
+  CHECK(fd->IsOpen()) << "Could not open \"" << path
+                      << "\" dir fd: " << fd->StrError();
+  return SetWorkingDirectory(fd);
+}
+Command Command::SetWorkingDirectory(std::string path) && {
+  auto fd = SharedFD::Open(path, O_RDONLY | O_PATH | O_DIRECTORY);
+  CHECK(fd->IsOpen()) << "Could not open \"" << path
+                      << "\" dir fd: " << fd->StrError();
+  return std::move(SetWorkingDirectory(fd));
+}
+Command& Command::SetWorkingDirectory(SharedFD dirfd) & {
+  CHECK(dirfd->IsOpen()) << "Dir fd invalid: " << dirfd->StrError();
+  working_directory_ = dirfd;
+  return *this;
+}
+Command Command::SetWorkingDirectory(SharedFD dirfd) && {
+  CHECK(dirfd->IsOpen()) << "Dir fd invalid: " << dirfd->StrError();
+  working_directory_ = dirfd;
+  return std::move(*this);
+}
 
 Subprocess Command::Start(SubprocessOptions options) const {
   auto cmd = ToCharPointers(command_);
@@ -242,18 +314,15 @@
         LOG(ERROR) << "fcntl failed: " << strerror(error_num);
       }
     }
-    int rval;
-    // If use_parent_env_ is false, the current process's environment is used as
-    // the environment of the child process. To force an empty emvironment for
-    // the child process pass the address of a pointer to NULL
-    if (use_parent_env_) {
-      UnsetEnvironment(unenv_);
-      rval = execvp(cmd[0], const_cast<char* const*>(cmd.data()));
-    } else {
-      auto envp = ToCharPointers(env_);
-      rval = execvpe(cmd[0], const_cast<char* const*>(cmd.data()),
-                    const_cast<char* const*>(envp.data()));
+    if (working_directory_->IsOpen()) {
+      if (SharedFD::Fchdir(working_directory_) != 0) {
+        LOG(ERROR) << "Fchdir failed: " << working_directory_->StrError();
+      }
     }
+    int rval;
+    auto envp = ToCharPointers(env_);
+    rval = execvpe(cmd[0], const_cast<char* const*>(cmd.data()),
+                   const_cast<char* const*>(envp.data()));
     // No need for an if: if exec worked it wouldn't have returned
     LOG(ERROR) << "exec of " << cmd[0] << " failed (" << strerror(errno)
                << ")";
@@ -276,6 +345,20 @@
   return Subprocess(pid, subprocess_stopper_);
 }
 
+std::string Command::AsBashScript(
+    const std::string& redirected_stdio_path) const {
+  CHECK(inherited_fds_.empty())
+      << "Bash wrapper will not have inheritied file descriptors.";
+  CHECK(redirects_.empty()) << "Bash wrapper will not have redirected stdio.";
+
+  std::string contents =
+      "#!/bin/bash\n\n" + android::base::Join(command_, " \\\n");
+  if (!redirected_stdio_path.empty()) {
+    contents += " &> " + AbsolutePath(redirected_stdio_path);
+  }
+  return contents;
+}
+
 // A class that waits for threads to exit in its destructor.
 class ThreadJoiner {
 std::vector<std::thread*> threads_;
@@ -290,8 +373,8 @@
   }
 };
 
-int RunWithManagedStdio(Command&& cmd_tmp, const std::string* stdin,
-                        std::string* stdout, std::string* stderr,
+int RunWithManagedStdio(Command&& cmd_tmp, const std::string* stdin_str,
+                        std::string* stdout_str, std::string* stderr_str,
                         SubprocessOptions options) {
   /*
    * The order of these declarations is necessary for safety. If the function
@@ -308,60 +391,48 @@
   ThreadJoiner thread_joiner({&stdin_thread, &stdout_thread, &stderr_thread});
   Command cmd = std::move(cmd_tmp);
   bool io_error = false;
-  if (stdin != nullptr) {
+  if (stdin_str != nullptr) {
     SharedFD pipe_read, pipe_write;
     if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
       LOG(ERROR) << "Could not create a pipe to write the stdin of \""
                 << cmd.GetShortName() << "\"";
       return -1;
     }
-    if (!cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read)) {
-      LOG(ERROR) << "Could not set stdout of \"" << cmd.GetShortName()
-                << "\", was already set.";
-      return -1;
-    }
-    stdin_thread = std::thread([pipe_write, stdin, &io_error]() {
-      int written = WriteAll(pipe_write, *stdin);
+    cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read);
+    stdin_thread = std::thread([pipe_write, stdin_str, &io_error]() {
+      int written = WriteAll(pipe_write, *stdin_str);
       if (written < 0) {
         io_error = true;
         LOG(ERROR) << "Error in writing stdin to process";
       }
     });
   }
-  if (stdout != nullptr) {
+  if (stdout_str != nullptr) {
     SharedFD pipe_read, pipe_write;
     if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
       LOG(ERROR) << "Could not create a pipe to read the stdout of \""
                 << cmd.GetShortName() << "\"";
       return -1;
     }
-    if (!cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write)) {
-      LOG(ERROR) << "Could not set stdout of \"" << cmd.GetShortName()
-                << "\", was already set.";
-      return -1;
-    }
-    stdout_thread = std::thread([pipe_read, stdout, &io_error]() {
-      int read = ReadAll(pipe_read, stdout);
+    cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write);
+    stdout_thread = std::thread([pipe_read, stdout_str, &io_error]() {
+      int read = ReadAll(pipe_read, stdout_str);
       if (read < 0) {
         io_error = true;
         LOG(ERROR) << "Error in reading stdout from process";
       }
     });
   }
-  if (stderr != nullptr) {
+  if (stderr_str != nullptr) {
     SharedFD pipe_read, pipe_write;
     if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
       LOG(ERROR) << "Could not create a pipe to read the stderr of \""
                 << cmd.GetShortName() << "\"";
       return -1;
     }
-    if (!cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write)) {
-      LOG(ERROR) << "Could not set stderr of \"" << cmd.GetShortName()
-                << "\", was already set.";
-      return -1;
-    }
-    stderr_thread = std::thread([pipe_read, stderr, &io_error]() {
-      int read = ReadAll(pipe_read, stderr);
+    cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write);
+    stderr_thread = std::thread([pipe_read, stderr_str, &io_error]() {
+      int read = ReadAll(pipe_read, stderr_str);
       if (read < 0) {
         io_error = true;
         LOG(ERROR) << "Error in reading stderr from process";
@@ -379,12 +450,8 @@
     // This is necessary to close the write end of the pipe.
     Command forceDelete = std::move(cmd);
   }
-  int wstatus;
-  subprocess.Wait(&wstatus, 0);
-  if (WIFSIGNALED(wstatus)) {
-    LOG(ERROR) << "Command was interrupted by a signal: " << WTERMSIG(wstatus);
-    return -1;
-  }
+
+  int code = subprocess.Wait();
   {
     auto join_threads = std::move(thread_joiner);
   }
@@ -392,7 +459,7 @@
     LOG(ERROR) << "IO error communicating with " << cmd_short_name;
     return -1;
   }
-  return WEXITSTATUS(wstatus);
+  return code;
 }
 
 int execute(const std::vector<std::string>& command,
diff --git a/common/libs/utils/subprocess.h b/common/libs/utils/subprocess.h
index 777a026..ff1af5b 100644
--- a/common/libs/utils/subprocess.h
+++ b/common/libs/utils/subprocess.h
@@ -16,25 +16,35 @@
 #pragma once
 
 #include <sys/types.h>
-
-#include <functional>
-#include <map>
-#include <sstream>
-#include <string>
-#include <unordered_set>
-#include <vector>
+#include <sys/wait.h>
 
 #include <android-base/logging.h>
+#include <android-base/strings.h>
 
-#include <common/libs/fs/shared_fd.h>
+#include <cstdio>
+#include <cstring>
+#include <functional>
+#include <map>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "common/libs/fs/shared_fd.h"
 
 namespace cuttlefish {
-class Command;
+
+enum class StopperResult {
+  kStopFailure, /* Failed to stop the subprocess. */
+  kStopCrash,   /* Attempted to stop the subprocess cleanly, but that failed. */
+  kStopSuccess, /* The subprocess exited in the expected way. */
+};
+
 class Subprocess;
-class SubprocessOptions;
-using SubprocessStopper = std::function<bool(Subprocess*)>;
+using SubprocessStopper = std::function<StopperResult(Subprocess*)>;
 // Kills a process by sending it the SIGKILL signal.
-bool KillSubprocess(Subprocess* subprocess);
+StopperResult KillSubprocess(Subprocess* subprocess);
 
 // Keeps track of a running (sub)process. Allows to wait for its completion.
 // It's an error to wait twice for the same subprocess.
@@ -58,14 +68,14 @@
   // Waits for the subprocess to complete. Returns zero if completed
   // successfully, non-zero otherwise.
   int Wait();
-  // Same as waitpid(2)
-  pid_t Wait(int* wstatus, int options);
+  // Same as waitid(2)
+  int Wait(siginfo_t* infop, int options);
   // Whether the command started successfully. It only says whether the call to
   // fork() succeeded or not, it says nothing about exec or successful
   // completion of the command, that's what Wait is for.
   bool Started() const { return started_; }
   pid_t pid() const { return pid_; }
-  bool Stop() { return stopper_(this); }
+  StopperResult Stop() { return stopper_(this); }
 
  private:
   // Copy is disabled to avoid waiting twice for the same pid (the first wait
@@ -79,26 +89,26 @@
 };
 
 class SubprocessOptions {
-  bool verbose_;
-  bool exit_with_parent_;
-  bool in_group_;
-public:
-  SubprocessOptions() : verbose_(true), exit_with_parent_(true) {}
+ public:
+  SubprocessOptions()
+      : verbose_(true), exit_with_parent_(true), in_group_(false) {}
 
-  void Verbose(bool verbose) {
-    verbose_ = verbose;
-  }
-  void ExitWithParent(bool exit_with_parent) {
-    exit_with_parent_ = exit_with_parent;
-  }
+  SubprocessOptions& Verbose(bool verbose) &;
+  SubprocessOptions Verbose(bool verbose) &&;
+  SubprocessOptions& ExitWithParent(bool exit_with_parent) &;
+  SubprocessOptions ExitWithParent(bool exit_with_parent) &&;
   // The subprocess runs as head of its own process group.
-  void InGroup(bool in_group) {
-    in_group_ = in_group;
-  }
+  SubprocessOptions& InGroup(bool in_group) &;
+  SubprocessOptions InGroup(bool in_group) &&;
 
   bool Verbose() const { return verbose_; }
   bool ExitWithParent() const { return exit_with_parent_; }
   bool InGroup() const { return in_group_; }
+
+ private:
+  bool verbose_;
+  bool exit_with_parent_;
+  bool in_group_;
 };
 
 // An executable command. Multiple subprocesses can be started from the same
@@ -108,15 +118,15 @@
  private:
   template <typename T>
   // For every type other than SharedFD (for which there is a specialisation)
-  bool BuildParameter(std::stringstream* stream, T t) {
+  void BuildParameter(std::stringstream* stream, T t) {
     *stream << t;
-    return true;
   }
   // Special treatment for SharedFD
-  bool BuildParameter(std::stringstream* stream, SharedFD shared_fd);
+  void BuildParameter(std::stringstream* stream, SharedFD shared_fd);
   template <typename T, typename... Args>
-  bool BuildParameter(std::stringstream* stream, T t, Args... args) {
-    return BuildParameter(stream, t) && BuildParameter(stream, args...);
+  void BuildParameter(std::stringstream* stream, T t, Args... args) {
+    BuildParameter(stream, t);
+    BuildParameter(stream, args...);
   }
 
  public:
@@ -124,10 +134,7 @@
   // optional subprocess stopper. When not provided, stopper defaults to sending
   // SIGKILL to the subprocess.
   Command(const std::string& executable,
-          SubprocessStopper stopper = KillSubprocess)
-      : subprocess_stopper_(stopper) {
-    command_.push_back(executable);
-  }
+          SubprocessStopper stopper = KillSubprocess);
   Command(Command&&) = default;
   // The default copy constructor is unsafe because it would mean multiple
   // closing of the inherited file descriptors. If needed it can be implemented
@@ -136,18 +143,72 @@
   Command& operator=(const Command&) = delete;
   ~Command();
 
-  // Specify the environment for the subprocesses to be started. By default
-  // subprocesses inherit the parent's environment.
-  void SetEnvironment(const std::vector<std::string>& env) {
-    use_parent_env_ = false;
-    env_ = env;
+  const std::string& Executable() const { return command_[0]; }
+
+  Command& SetExecutable(const std::string& executable) & {
+    command_[0] = executable;
+    return *this;
+  }
+  Command SetExecutable(const std::string& executable) && {
+    SetExecutable(executable);
+    return std::move(*this);
   }
 
-  // Specify environment variables to be unset from the parent's environment
-  // for the subprocesses to be started.
-  void UnsetFromEnvironment(const std::vector<std::string>& env) {
-    use_parent_env_ = true;
-    std::copy(env.cbegin(), env.cend(), std::inserter(unenv_, unenv_.end()));
+  Command& SetStopper(SubprocessStopper stopper) & {
+    subprocess_stopper_ = stopper;
+    return *this;
+  }
+  Command SetStopper(SubprocessStopper stopper) && {
+    SetStopper(stopper);
+    return std::move(*this);
+  }
+
+  // Specify the environment for the subprocesses to be started. By default
+  // subprocesses inherit the parent's environment.
+  Command& SetEnvironment(const std::vector<std::string>& env) & {
+    env_ = env;
+    return *this;
+  }
+  Command SetEnvironment(const std::vector<std::string>& env) && {
+    SetEnvironment(env);
+    return std::move(*this);
+  }
+
+  Command& AddEnvironmentVariable(const std::string& env_var,
+                                  const std::string& value) & {
+    return AddEnvironmentVariable(env_var + "=" + value);
+  }
+  Command AddEnvironmentVariable(const std::string& env_var,
+                                 const std::string& value) && {
+    AddEnvironmentVariable(env_var, value);
+    return std::move(*this);
+  }
+
+  Command& AddEnvironmentVariable(const std::string& env_var) & {
+    env_.push_back(env_var);
+    return *this;
+  }
+  Command AddEnvironmentVariable(const std::string& env_var) && {
+    AddEnvironmentVariable(env_var);
+    return std::move(*this);
+  }
+
+  // Specify an environment variable to be unset from the parent's
+  // environment for the subprocesses to be started.
+  Command& UnsetFromEnvironment(const std::string& env_var) & {
+    auto it = env_.begin();
+    while (it != env_.end()) {
+      if (android::base::StartsWith(*it, env_var + "=")) {
+        it = env_.erase(it);
+      } else {
+        ++it;
+      }
+    }
+    return *this;
+  }
+  Command UnsetFromEnvironment(const std::string& env_var) && {
+    UnsetFromEnvironment(env_var);
+    return std::move(*this);
   }
 
   // Adds a single parameter to the command. All arguments are concatenated into
@@ -156,34 +217,47 @@
   // object is destroyed. To add multiple parameters to the command the function
   // must be called multiple times, one per parameter.
   template <typename... Args>
-  bool AddParameter(Args... args) {
+  Command& AddParameter(Args... args) & {
     std::stringstream ss;
-    if (BuildParameter(&ss, args...)) {
-      command_.push_back(ss.str());
-      return true;
-    }
-    return false;
+    BuildParameter(&ss, args...);
+    command_.push_back(ss.str());
+    return *this;
+  }
+  template <typename... Args>
+  Command AddParameter(Args... args) && {
+    AddParameter(std::forward<Args>(args)...);
+    return std::move(*this);
   }
   // Similar to AddParameter, except the args are appended to the last (most
   // recently-added) parameter in the command.
   template <typename... Args>
-  bool AppendToLastParameter(Args... args) {
-    if (command_.empty()) {
-      LOG(ERROR) << "There is no parameter to append to.";
-      return false;
-    }
+  Command& AppendToLastParameter(Args... args) & {
+    CHECK(!command_.empty()) << "There is no parameter to append to.";
     std::stringstream ss;
-    if (BuildParameter(&ss, args...)) {
-      command_[command_.size()-1] += ss.str();
-      return true;
-    }
-    return false;
+    BuildParameter(&ss, args...);
+    command_[command_.size() - 1] += ss.str();
+    return *this;
+  }
+  template <typename... Args>
+  Command AppendToLastParameter(Args... args) && {
+    AppendToLastParameter(std::forward<Args>(args)...);
+    return std::move(*this);
   }
 
   // Redirects the standard IO of the command.
-  bool RedirectStdIO(Subprocess::StdIOChannel channel, SharedFD shared_fd);
-  bool RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
-                     Subprocess::StdIOChannel parent_channel);
+  Command& RedirectStdIO(Subprocess::StdIOChannel channel,
+                         SharedFD shared_fd) &;
+  Command RedirectStdIO(Subprocess::StdIOChannel channel,
+                        SharedFD shared_fd) &&;
+  Command& RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
+                         Subprocess::StdIOChannel parent_channel) &;
+  Command RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
+                        Subprocess::StdIOChannel parent_channel) &&;
+
+  Command& SetWorkingDirectory(std::string path) &;
+  Command SetWorkingDirectory(std::string path) &&;
+  Command& SetWorkingDirectory(SharedFD dirfd) &;
+  Command SetWorkingDirectory(SharedFD dirfd) &&;
 
   // Starts execution of the command. This method can be called multiple times,
   // effectively staring multiple (possibly concurrent) instances.
@@ -195,14 +269,19 @@
     return command_[0];
   }
 
+  // Generates the contents for a bash script that can be used to run this
+  // command. Note that this command must not require any file descriptors
+  // or stdio redirects as those would not be available when the bash script
+  // is run.
+  std::string AsBashScript(const std::string& redirected_stdio_path = "") const;
+
  private:
   std::vector<std::string> command_;
   std::map<SharedFD, int> inherited_fds_{};
   std::map<Subprocess::StdIOChannel, int> redirects_{};
-  bool use_parent_env_ = true;
   std::vector<std::string> env_{};
-  std::unordered_set<std::string> unenv_{};
   SubprocessStopper subprocess_stopper_;
+  SharedFD working_directory_;
 };
 
 /*
diff --git a/common/libs/utils/tcp_socket.cpp b/common/libs/utils/tcp_socket.cpp
index 3b56725..db96232 100644
--- a/common/libs/utils/tcp_socket.cpp
+++ b/common/libs/utils/tcp_socket.cpp
@@ -18,9 +18,12 @@
 
 #include <netinet/in.h>
 #include <sys/socket.h>
-#include <sys/types.h>
 
 #include <cerrno>
+#include <cstring>
+#include <memory>
+#include <ostream>
+#include <string>
 
 #include <android-base/logging.h>
 
diff --git a/common/libs/utils/tcp_socket.h b/common/libs/utils/tcp_socket.h
index 292decd..22827ed 100644
--- a/common/libs/utils/tcp_socket.h
+++ b/common/libs/utils/tcp_socket.h
@@ -23,13 +23,12 @@
 #include <cstddef>
 #include <cstdint>
 #include <mutex>
+#include <string>
 #include <vector>
 
 namespace cuttlefish {
 using Message = std::vector<std::uint8_t>;
 
-class ServerSocket;
-
 // Recv and Send wait until all data has been received or sent.
 // Send is thread safe in this regard, Recv is not.
 class ClientSocket {
@@ -62,7 +61,7 @@
   bool closed() const;
 
  private:
-  friend ServerSocket;
+  friend class ServerSocket;
   explicit ClientSocket(SharedFD fd) : fd_(fd) {}
 
   SharedFD fd_;
diff --git a/common/libs/utils/tee_logging.cpp b/common/libs/utils/tee_logging.cpp
index 66b46c1..8129b40 100644
--- a/common/libs/utils/tee_logging.cpp
+++ b/common/libs/utils/tee_logging.cpp
@@ -15,9 +15,22 @@
 
 #include "tee_logging.h"
 
-#include <stdlib.h>
-#include <inttypes.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
 
+#include <cinttypes>
+#include <cstring>
+#include <ctime>
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/macros.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/threads.h>
@@ -77,9 +90,9 @@
   return GuessSeverity("CF_FILE_SEVERITY", android::base::DEBUG);
 }
 
-TeeLogger::TeeLogger(const std::vector<SeverityTarget>& destinations)
-    : destinations_(destinations) {
-}
+TeeLogger::TeeLogger(const std::vector<SeverityTarget>& destinations,
+                     const std::string& prefix)
+    : destinations_(destinations), prefix_(prefix) {}
 
 // Copied from system/libbase/logging_splitters.h
 static std::pair<int, int> CountSizeAndNewLines(const char* message) {
@@ -175,15 +188,17 @@
     unsigned int line,
     const char* message) {
   for (const auto& destination : destinations_) {
+    std::string msg_with_prefix = prefix_ + message;
     std::string output_string;
     if (destination.metadata_level == MetadataLevel::ONLY_MESSAGE) {
-      output_string = message + std::string("\n");
+      output_string = msg_with_prefix + std::string("\n");
     } else {
       struct tm now;
       time_t t = time(nullptr);
       localtime_r(&t, &now);
-      output_string = StderrOutputGenerator(now, getpid(), GetThreadId(),
-                                            severity, tag, file, line, message);
+      output_string =
+          StderrOutputGenerator(now, getpid(), GetThreadId(), severity, tag,
+                                file, line, msg_with_prefix.c_str());
     }
     if (severity >= destination.severity) {
       if (destination.target->IsATTY()) {
@@ -213,16 +228,18 @@
   return log_severities;
 }
 
-TeeLogger LogToFiles(const std::vector<std::string>& files) {
-  return TeeLogger(SeverityTargetsForFiles(files));
+TeeLogger LogToFiles(const std::vector<std::string>& files,
+                     const std::string& prefix) {
+  return TeeLogger(SeverityTargetsForFiles(files), prefix);
 }
 
-TeeLogger LogToStderrAndFiles(const std::vector<std::string>& files) {
+TeeLogger LogToStderrAndFiles(const std::vector<std::string>& files,
+                              const std::string& prefix) {
   std::vector<SeverityTarget> log_severities = SeverityTargetsForFiles(files);
   log_severities.push_back(SeverityTarget{ConsoleSeverity(),
                                           SharedFD::Dup(/* stderr */ 2),
                                           MetadataLevel::ONLY_MESSAGE});
-  return TeeLogger(log_severities);
+  return TeeLogger(log_severities, prefix);
 }
 
 } // namespace cuttlefish
diff --git a/common/libs/utils/tee_logging.h b/common/libs/utils/tee_logging.h
index 6306e62..ac3e70c 100644
--- a/common/libs/utils/tee_logging.h
+++ b/common/libs/utils/tee_logging.h
@@ -42,19 +42,21 @@
 private:
   std::vector<SeverityTarget> destinations_;
 public:
-  TeeLogger(const std::vector<SeverityTarget>& destinations);
-  ~TeeLogger() = default;
+ TeeLogger(const std::vector<SeverityTarget>& destinations,
+           const std::string& log_prefix = "");
+ ~TeeLogger() = default;
 
-  void operator()(
-      android::base::LogId log_id,
-      android::base::LogSeverity severity,
-      const char* tag,
-      const char* file,
-      unsigned int line,
-      const char* message);
+ void operator()(android::base::LogId log_id,
+                 android::base::LogSeverity severity, const char* tag,
+                 const char* file, unsigned int line, const char* message);
+
+private:
+ std::string prefix_;
 };
 
-TeeLogger LogToFiles(const std::vector<std::string>& files);
-TeeLogger LogToStderrAndFiles(const std::vector<std::string>& files);
+TeeLogger LogToFiles(const std::vector<std::string>& files,
+                     const std::string& log_prefix = "");
+TeeLogger LogToStderrAndFiles(const std::vector<std::string>& files,
+                              const std::string& log_prefix = "");
 
 } // namespace cuttlefish
diff --git a/common/libs/utils/unix_sockets.cpp b/common/libs/utils/unix_sockets.cpp
new file mode 100644
index 0000000..cd638c5
--- /dev/null
+++ b/common/libs/utils/unix_sockets.cpp
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "common/libs/utils/unix_sockets.h"
+
+#include <fcntl.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <cstring>
+#include <memory>
+#include <ostream>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+// This would use android::base::ReceiveFileDescriptors, but it silently drops
+// SCM_CREDENTIALS control messages.
+
+namespace cuttlefish {
+
+ControlMessage ControlMessage::FromRaw(const cmsghdr* cmsg) {
+  ControlMessage message;
+  message.data_ =
+      std::vector<char>((char*)cmsg, ((char*)cmsg) + cmsg->cmsg_len);
+  if (message.IsFileDescriptors()) {
+    size_t fdcount =
+        static_cast<size_t>(cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+    for (int i = 0; i < fdcount; i++) {
+      // Use memcpy as CMSG_DATA may be unaligned
+      int fd = -1;
+      memcpy(&fd, CMSG_DATA(cmsg) + (i * sizeof(int)), sizeof(fd));
+      message.fds_.push_back(fd);
+    }
+  }
+  return message;
+}
+
+Result<ControlMessage> ControlMessage::FromFileDescriptors(
+    const std::vector<SharedFD>& fds) {
+  ControlMessage message;
+  message.data_.resize(CMSG_SPACE(fds.size() * sizeof(int)), 0);
+  message.Raw()->cmsg_len = CMSG_LEN(fds.size() * sizeof(int));
+  message.Raw()->cmsg_level = SOL_SOCKET;
+  message.Raw()->cmsg_type = SCM_RIGHTS;
+  for (int i = 0; i < fds.size(); i++) {
+    int fd_copy = fds[i]->Fcntl(F_DUPFD_CLOEXEC, 3);
+    CF_EXPECT(fd_copy >= 0, "Failed to duplicate fd: " << fds[i]->StrError());
+    message.fds_.push_back(fd_copy);
+    // Following the CMSG_DATA spec, use memcpy to avoid alignment issues.
+    memcpy(CMSG_DATA(message.Raw()) + (i * sizeof(int)), &fd_copy, sizeof(int));
+  }
+  return message;
+}
+
+ControlMessage ControlMessage::FromCredentials(const ucred& credentials) {
+  ControlMessage message;
+  message.data_.resize(CMSG_SPACE(sizeof(ucred)), 0);
+  message.Raw()->cmsg_len = CMSG_LEN(sizeof(ucred));
+  message.Raw()->cmsg_level = SOL_SOCKET;
+  message.Raw()->cmsg_type = SCM_CREDENTIALS;
+  // Following the CMSG_DATA spec, use memcpy to avoid alignment issues.
+  memcpy(CMSG_DATA(message.Raw()), &credentials, sizeof(credentials));
+  return message;
+}
+
+ControlMessage::ControlMessage(ControlMessage&& existing) {
+  // Enforce that the old ControlMessage is left empty, so it doesn't try to
+  // close any file descriptors. https://stackoverflow.com/a/17735913
+  data_ = std::move(existing.data_);
+  existing.data_.clear();
+  fds_ = std::move(existing.fds_);
+  existing.fds_.clear();
+}
+
+ControlMessage& ControlMessage::operator=(ControlMessage&& existing) {
+  // Enforce that the old ControlMessage is left empty, so it doesn't try to
+  // close any file descriptors. https://stackoverflow.com/a/17735913
+  data_ = std::move(existing.data_);
+  existing.data_.clear();
+  fds_ = std::move(existing.fds_);
+  existing.fds_.clear();
+  return *this;
+}
+
+ControlMessage::~ControlMessage() {
+  for (const auto& fd : fds_) {
+    if (close(fd) != 0) {
+      PLOG(ERROR) << "Failed to close fd " << fd
+                  << ", may have leaked or closed prematurely";
+    }
+  }
+}
+
+cmsghdr* ControlMessage::Raw() {
+  return reinterpret_cast<cmsghdr*>(data_.data());
+}
+
+const cmsghdr* ControlMessage::Raw() const {
+  return reinterpret_cast<const cmsghdr*>(data_.data());
+}
+
+bool ControlMessage::IsCredentials() const {
+  bool right_level = Raw()->cmsg_level == SOL_SOCKET;
+  bool right_type = Raw()->cmsg_type == SCM_CREDENTIALS;
+  bool enough_data = Raw()->cmsg_len >= sizeof(cmsghdr) + sizeof(ucred);
+  return right_level && right_type && enough_data;
+}
+
+Result<ucred> ControlMessage::AsCredentials() const {
+  CF_EXPECT(IsCredentials(), "Control message does not hold a credential");
+  ucred credentials;
+  memcpy(&credentials, CMSG_DATA(Raw()), sizeof(ucred));
+  return credentials;
+}
+
+bool ControlMessage::IsFileDescriptors() const {
+  bool right_level = Raw()->cmsg_level == SOL_SOCKET;
+  bool right_type = Raw()->cmsg_type == SCM_RIGHTS;
+  return right_level && right_type;
+}
+
+Result<std::vector<SharedFD>> ControlMessage::AsSharedFDs() const {
+  CF_EXPECT(IsFileDescriptors(), "Message does not contain file descriptors");
+  size_t fdcount =
+      static_cast<size_t>(Raw()->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+  std::vector<SharedFD> shared_fds;
+  for (int i = 0; i < fdcount; i++) {
+    // Use memcpy as CMSG_DATA may be unaligned
+    int fd = -1;
+    memcpy(&fd, CMSG_DATA(Raw()) + (i * sizeof(int)), sizeof(fd));
+    SharedFD shared_fd = SharedFD::Dup(fd);
+    CF_EXPECT(shared_fd->IsOpen(), "Could not dup FD " << fd);
+    shared_fds.push_back(shared_fd);
+  }
+  return shared_fds;
+}
+
+bool UnixSocketMessage::HasFileDescriptors() {
+  for (const auto& control_message : control) {
+    if (control_message.IsFileDescriptors()) {
+      return true;
+    }
+  }
+  return false;
+}
+Result<std::vector<SharedFD>> UnixSocketMessage::FileDescriptors() {
+  std::vector<SharedFD> fds;
+  for (const auto& control_message : control) {
+    if (control_message.IsFileDescriptors()) {
+      auto additional_fds = CF_EXPECT(control_message.AsSharedFDs());
+      fds.insert(fds.end(), additional_fds.begin(), additional_fds.end());
+    }
+  }
+  return fds;
+}
+bool UnixSocketMessage::HasCredentials() {
+  for (const auto& control_message : control) {
+    if (control_message.IsCredentials()) {
+      return true;
+    }
+  }
+  return false;
+}
+Result<ucred> UnixSocketMessage::Credentials() {
+  std::vector<ucred> credentials;
+  for (const auto& control_message : control) {
+    if (control_message.IsCredentials()) {
+      auto creds = CF_EXPECT(control_message.AsCredentials(),
+                             "Message claims to have credentials but does not");
+      credentials.push_back(creds);
+    }
+  }
+  if (credentials.size() == 0) {
+    return CF_ERR("No credentials present");
+  } else if (credentials.size() == 1) {
+    return credentials[0];
+  } else {
+    return CF_ERR("Excepted 1 credential, received " << credentials.size());
+  }
+}
+
+UnixMessageSocket::UnixMessageSocket(SharedFD socket) : socket_(socket) {
+  socklen_t ln = sizeof(max_message_size_);
+  CHECK(socket->GetSockOpt(SOL_SOCKET, SO_SNDBUF, &max_message_size_, &ln) == 0)
+      << "error: can't retrieve socket max message size: "
+      << socket->StrError();
+}
+
+Result<void> UnixMessageSocket::EnableCredentials(bool enable) {
+  int flag = enable ? 1 : 0;
+  if (socket_->SetSockOpt(SOL_SOCKET, SO_PASSCRED, &flag, sizeof(flag)) != 0) {
+    return CF_ERR("Could not set credential status to " << enable << ": "
+                                                        << socket_->StrError());
+  }
+  return {};
+}
+
+Result<void> UnixMessageSocket::WriteMessage(const UnixSocketMessage& message) {
+  auto control_size = 0;
+  for (const auto& control : message.control) {
+    control_size += control.data_.size();
+  }
+  std::vector<char> message_control(control_size, 0);
+  msghdr message_header{};
+  message_header.msg_control = message_control.data();
+  message_header.msg_controllen = message_control.size();
+  auto cmsg = CMSG_FIRSTHDR(&message_header);
+  for (const ControlMessage& control : message.control) {
+    CF_EXPECT(cmsg != nullptr,
+              "Control messages did not fit in control buffer");
+    /* size() should match CMSG_SPACE */
+    memcpy(cmsg, control.data_.data(), control.data_.size());
+    cmsg = CMSG_NXTHDR(&message_header, cmsg);
+  }
+
+  iovec message_iovec;
+  message_iovec.iov_base = (void*)message.data.data();
+  message_iovec.iov_len = message.data.size();
+  message_header.msg_name = nullptr;
+  message_header.msg_namelen = 0;
+  message_header.msg_iov = &message_iovec;
+  message_header.msg_iovlen = 1;
+  message_header.msg_flags = 0;
+
+  auto bytes_sent = socket_->SendMsg(&message_header, MSG_NOSIGNAL);
+  CF_EXPECT(bytes_sent >= 0, "Failed to send message: " << socket_->StrError());
+  CF_EXPECT(bytes_sent == message.data.size(),
+            "Failed to send entire message. Sent "
+                << bytes_sent << ", excepted to send " << message.data.size());
+  return {};
+}
+
+Result<UnixSocketMessage> UnixMessageSocket::ReadMessage() {
+  msghdr message_header{};
+  std::vector<char> message_control(max_message_size_, 0);
+  message_header.msg_control = message_control.data();
+  message_header.msg_controllen = message_control.size();
+  std::vector<char> message_data(max_message_size_, 0);
+  iovec message_iovec;
+  message_iovec.iov_base = message_data.data();
+  message_iovec.iov_len = message_data.size();
+  message_header.msg_iov = &message_iovec;
+  message_header.msg_iovlen = 1;
+  message_header.msg_name = nullptr;
+  message_header.msg_namelen = 0;
+  message_header.msg_flags = 0;
+
+  auto bytes_read = socket_->RecvMsg(&message_header, MSG_CMSG_CLOEXEC);
+  CF_EXPECT(bytes_read >= 0, "Read error: " << socket_->StrError());
+  CF_EXPECT(!(message_header.msg_flags & MSG_TRUNC),
+            "Message was truncated on read");
+  CF_EXPECT(!(message_header.msg_flags & MSG_CTRUNC),
+            "Message control data was truncated on read");
+  CF_EXPECT(!(message_header.msg_flags & MSG_ERRQUEUE), "Error queue error");
+  UnixSocketMessage managed_message;
+  for (auto cmsg = CMSG_FIRSTHDR(&message_header); cmsg != nullptr;
+       cmsg = CMSG_NXTHDR(&message_header, cmsg)) {
+    managed_message.control.emplace_back(ControlMessage::FromRaw(cmsg));
+  }
+  message_data.resize(bytes_read);
+  managed_message.data = std::move(message_data);
+
+  return managed_message;
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/unix_sockets.h b/common/libs/utils/unix_sockets.h
new file mode 100644
index 0000000..4c9881a
--- /dev/null
+++ b/common/libs/utils/unix_sockets.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <sys/socket.h>
+
+#include <cstdint>
+#include <vector>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+struct ControlMessage {
+ public:
+  static ControlMessage FromRaw(const cmsghdr*);
+  static Result<ControlMessage> FromFileDescriptors(
+      const std::vector<SharedFD>&);
+  static ControlMessage FromCredentials(const ucred&);
+  ControlMessage(const ControlMessage&) = delete;
+  ControlMessage(ControlMessage&&);
+  ~ControlMessage();
+  ControlMessage& operator=(const ControlMessage&) = delete;
+  ControlMessage& operator=(ControlMessage&&);
+
+  const cmsghdr* Raw() const;
+
+  bool IsCredentials() const;
+  Result<ucred> AsCredentials() const;
+
+  bool IsFileDescriptors() const;
+  Result<std::vector<SharedFD>> AsSharedFDs() const;
+
+ private:
+  friend class UnixMessageSocket;
+  ControlMessage() = default;
+  cmsghdr* Raw();
+
+  std::vector<char> data_;
+  std::vector<int> fds_;
+};
+
+struct UnixSocketMessage {
+  std::vector<char> data;
+  std::vector<ControlMessage> control;
+
+  bool HasFileDescriptors();
+  Result<std::vector<SharedFD>> FileDescriptors();
+  bool HasCredentials();
+  Result<ucred> Credentials();
+};
+
+class UnixMessageSocket {
+ public:
+  UnixMessageSocket(SharedFD);
+  [[nodiscard]] Result<void> WriteMessage(const UnixSocketMessage&);
+  Result<UnixSocketMessage> ReadMessage();
+
+  [[nodiscard]] Result<void> EnableCredentials(bool);
+
+ private:
+  SharedFD socket_;
+  std::uint32_t max_message_size_;
+};
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/unix_sockets_test.cpp b/common/libs/utils/unix_sockets_test.cpp
new file mode 100644
index 0000000..4475064
--- /dev/null
+++ b/common/libs/utils/unix_sockets_test.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common/libs/utils/unix_sockets.h"
+
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <gtest/gtest.h>
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+
+SharedFD CreateMemFDWithData(const std::string& data) {
+  auto memfd = SharedFD::MemfdCreate("");
+  CHECK(WriteAll(memfd, data) == data.size()) << memfd->StrError();
+  CHECK(memfd->LSeek(0, SEEK_SET) == 0);
+  return memfd;
+}
+
+std::string ReadAllFDData(SharedFD fd) {
+  std::string data;
+  CHECK(ReadAll(fd, &data) > 0) << fd->StrError();
+  return data;
+}
+
+TEST(UnixSocketMessage, ExtractFileDescriptors) {
+  auto memfd1 = CreateMemFDWithData("abc");
+  auto memfd2 = CreateMemFDWithData("def");
+
+  UnixSocketMessage message;
+  auto control1 = ControlMessage::FromFileDescriptors({memfd1});
+  ASSERT_TRUE(control1.ok()) << control1.error();
+  message.control.emplace_back(std::move(*control1));
+  auto control2 = ControlMessage::FromFileDescriptors({memfd2});
+  ASSERT_TRUE(control2.ok()) << control2.error();
+  message.control.emplace_back(std::move(*control2));
+
+  ASSERT_TRUE(message.HasFileDescriptors());
+  auto fds = message.FileDescriptors();
+  ASSERT_TRUE(fds.ok());
+  ASSERT_EQ("abc", ReadAllFDData((*fds)[0]));
+  ASSERT_EQ("def", ReadAllFDData((*fds)[1]));
+}
+
+std::pair<UnixMessageSocket, UnixMessageSocket> UnixMessageSocketPair() {
+  SharedFD sock1, sock2;
+  CHECK(SharedFD::SocketPair(AF_UNIX, SOCK_SEQPACKET, 0, &sock1, &sock2));
+  return {UnixMessageSocket(sock1), UnixMessageSocket(sock2)};
+}
+
+TEST(UnixMessageSocket, SendPlainMessage) {
+  auto [writer, reader] = UnixMessageSocketPair();
+  UnixSocketMessage message_in = {{1, 2, 3}, {}};
+  auto write_result = writer.WriteMessage(message_in);
+  ASSERT_TRUE(write_result.ok()) << write_result.error();
+
+  auto message_out = reader.ReadMessage();
+  ASSERT_TRUE(message_out.ok()) << message_out.error();
+  ASSERT_EQ(message_in.data, message_out->data);
+  ASSERT_EQ(0, message_out->control.size());
+}
+
+TEST(UnixMessageSocket, SendFileDescriptor) {
+  auto [writer, reader] = UnixMessageSocketPair();
+
+  UnixSocketMessage message_in = {{4, 5, 6}, {}};
+  auto control_in =
+      ControlMessage::FromFileDescriptors({CreateMemFDWithData("abc")});
+  ASSERT_TRUE(control_in.ok()) << control_in.error();
+  message_in.control.emplace_back(std::move(*control_in));
+  auto write_result = writer.WriteMessage(message_in);
+  ASSERT_TRUE(write_result.ok()) << write_result.error();
+
+  auto message_out = reader.ReadMessage();
+  ASSERT_TRUE(message_out.ok()) << message_out.error();
+  ASSERT_EQ(message_in.data, message_out->data);
+
+  ASSERT_EQ(1, message_out->control.size());
+  auto fds_out = message_out->control[0].AsSharedFDs();
+  ASSERT_TRUE(fds_out.ok()) << fds_out.error();
+  ASSERT_EQ(1, fds_out->size());
+  ASSERT_EQ("abc", ReadAllFDData((*fds_out)[0]));
+}
+
+TEST(UnixMessageSocket, SendTwoFileDescriptors) {
+  auto memfd1 = CreateMemFDWithData("abc");
+  auto memfd2 = CreateMemFDWithData("def");
+
+  auto [writer, reader] = UnixMessageSocketPair();
+  UnixSocketMessage message_in = {{7, 8, 9}, {}};
+  auto control_in = ControlMessage::FromFileDescriptors({memfd1, memfd2});
+  ASSERT_TRUE(control_in.ok()) << control_in.error();
+  message_in.control.emplace_back(std::move(*control_in));
+  auto write_result = writer.WriteMessage(message_in);
+  ASSERT_TRUE(write_result.ok()) << write_result.error();
+
+  auto message_out = reader.ReadMessage();
+  ASSERT_TRUE(message_out.ok()) << message_out.error();
+  ASSERT_EQ(message_in.data, message_out->data);
+
+  ASSERT_EQ(1, message_out->control.size());
+  auto fds_out = message_out->control[0].AsSharedFDs();
+  ASSERT_TRUE(fds_out.ok()) << fds_out.error();
+  ASSERT_EQ(2, fds_out->size());
+
+  ASSERT_EQ("abc", ReadAllFDData((*fds_out)[0]));
+  ASSERT_EQ("def", ReadAllFDData((*fds_out)[1]));
+}
+
+TEST(UnixMessageSocket, SendCredentials) {
+  auto [writer, reader] = UnixMessageSocketPair();
+  auto writer_creds_status = writer.EnableCredentials(true);
+  ASSERT_TRUE(writer_creds_status.ok()) << writer_creds_status.error();
+  auto reader_creds_status = reader.EnableCredentials(true);
+  ASSERT_TRUE(reader_creds_status.ok()) << reader_creds_status.error();
+
+  ucred credentials_in;
+  credentials_in.pid = getpid();
+  credentials_in.uid = getuid();
+  credentials_in.gid = getgid();
+  UnixSocketMessage message_in = {{1, 5, 9}, {}};
+  auto control_in = ControlMessage::FromCredentials(credentials_in);
+  message_in.control.emplace_back(std::move(control_in));
+  auto write_result = writer.WriteMessage(message_in);
+  ASSERT_TRUE(write_result.ok()) << write_result.error();
+
+  auto message_out = reader.ReadMessage();
+  ASSERT_TRUE(message_out.ok()) << message_out.error();
+  ASSERT_EQ(message_in.data, message_out->data);
+
+  ASSERT_EQ(1, message_out->control.size());
+  auto credentials_out = message_out->control[0].AsCredentials();
+  ASSERT_TRUE(credentials_out.ok()) << credentials_out.error();
+  ASSERT_EQ(credentials_in.pid, credentials_out->pid);
+  ASSERT_EQ(credentials_in.uid, credentials_out->uid);
+  ASSERT_EQ(credentials_in.gid, credentials_out->gid);
+}
+
+TEST(UnixMessageSocket, BadCredentialsBlocked) {
+  auto [writer, reader] = UnixMessageSocketPair();
+  auto writer_creds_status = writer.EnableCredentials(true);
+  ASSERT_TRUE(writer_creds_status.ok()) << writer_creds_status.error();
+  auto reader_creds_status = reader.EnableCredentials(true);
+  ASSERT_TRUE(reader_creds_status.ok()) << reader_creds_status.error();
+
+  ucred credentials_in;
+  // This assumes the test is running without root privileges
+  credentials_in.pid = getpid() + 1;
+  credentials_in.uid = getuid() + 1;
+  credentials_in.gid = getgid() + 1;
+
+  UnixSocketMessage message_in = {{2, 4, 6}, {}};
+  auto control_in = ControlMessage::FromCredentials(credentials_in);
+  message_in.control.emplace_back(std::move(control_in));
+  auto write_result = writer.WriteMessage(message_in);
+  ASSERT_FALSE(write_result.ok()) << write_result.error();
+}
+
+TEST(UnixMessageSocket, AutoCredentials) {
+  auto [writer, reader] = UnixMessageSocketPair();
+  auto writer_creds_status = writer.EnableCredentials(true);
+  ASSERT_TRUE(writer_creds_status.ok()) << writer_creds_status.error();
+  auto reader_creds_status = reader.EnableCredentials(true);
+  ASSERT_TRUE(reader_creds_status.ok()) << reader_creds_status.error();
+
+  UnixSocketMessage message_in = {{3, 6, 9}, {}};
+  auto write_result = writer.WriteMessage(message_in);
+  ASSERT_TRUE(write_result.ok()) << write_result.error();
+
+  auto message_out = reader.ReadMessage();
+  ASSERT_TRUE(message_out.ok()) << message_out.error();
+  ASSERT_EQ(message_in.data, message_out->data);
+
+  ASSERT_EQ(1, message_out->control.size());
+  auto credentials_out = message_out->control[0].AsCredentials();
+  ASSERT_TRUE(credentials_out.ok()) << credentials_out.error();
+  ASSERT_EQ(getpid(), credentials_out->pid);
+  ASSERT_EQ(getuid(), credentials_out->uid);
+  ASSERT_EQ(getgid(), credentials_out->gid);
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/users.cpp b/common/libs/utils/users.cpp
index 31a4a5e..a3699de 100644
--- a/common/libs/utils/users.cpp
+++ b/common/libs/utils/users.cpp
@@ -16,13 +16,14 @@
 
 #include "common/libs/utils/users.h"
 
-#include <cerrno>
-#include <cstring>
-#include <sys/types.h>
-#include <unistd.h>
 #include <grp.h>
+#include <unistd.h>
 
 #include <algorithm>
+#include <cerrno>
+#include <cstring>
+#include <ostream>
+#include <string>
 #include <vector>
 
 #include <android-base/logging.h>
diff --git a/common/libs/utils/vsock_connection.cpp b/common/libs/utils/vsock_connection.cpp
index 6142b69..fbdb02b 100644
--- a/common/libs/utils/vsock_connection.cpp
+++ b/common/libs/utils/vsock_connection.cpp
@@ -16,11 +16,26 @@
 
 #include "common/libs/utils/vsock_connection.h"
 
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <functional>
+#include <future>
+#include <memory>
+#include <mutex>
+#include <new>
+#include <ostream>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <json/json.h>
+
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_select.h"
 
-#include "android-base/logging.h"
-
 namespace cuttlefish {
 
 VsockConnection::~VsockConnection() { Disconnect(); }
diff --git a/common/libs/utils/vsock_connection.h b/common/libs/utils/vsock_connection.h
index 1a16b2f..c7a796a 100644
--- a/common/libs/utils/vsock_connection.h
+++ b/common/libs/utils/vsock_connection.h
@@ -15,11 +15,16 @@
  */
 #pragma once
 
-#include <json/json.h>
+#include <cstddef>
+#include <cstdint>
 #include <functional>
 #include <future>
 #include <mutex>
+#include <string>
 #include <vector>
+
+#include <json/json.h>
+
 #include "common/libs/fs/shared_fd.h"
 
 namespace cuttlefish {
diff --git a/default-permissions.xml b/default-permissions.xml
index eb50a4d..48b0f17 100644
--- a/default-permissions.xml
+++ b/default-permissions.xml
@@ -54,7 +54,11 @@
         <permission name="android.permission.READ_CALL_LOG" fixed="false"/>
         <permission name="android.permission.WRITE_CALL_LOG" fixed="false"/>
         <!-- Used to set up a Wi-Fi P2P network -->
+        <!-- TODO(b/231966826): Remove the location permission after Restore targets to T. -->
         <permission name="android.permission.ACCESS_FINE_LOCATION" fixed="false"/>
+        <permission name="android.permission.NEARBY_WIFI_DEVICES" fixed="false"/>
+        <!-- Notifications -->
+        <permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
     </exception>
 
     <exception
@@ -104,4 +108,29 @@
         <permission name="android.permission.READ_EXTERNAL_STORAGE" fixed="false"/>
         <permission name="android.permission.WRITE_EXTERNAL_STORAGE" fixed="false"/>
     </exception>
+    <exception
+        package="com.google.android.deskclock">
+        <!-- Notifications -->
+        <permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
+    </exception>
+    <exception
+        package="com.google.android.apps.tips">
+        <!-- Notifications -->
+        <permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
+    </exception>
+    <exception
+        package="com.google.android.adservices">
+        <!-- Notifications -->
+        <permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
+    </exception>
+    <exception
+        package="com.google.android.apps.mediashell">
+        <!-- Notifications -->
+        <permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
+    </exception>
+    <exception
+        package="com.google.android.apps.pixelmigrate">
+        <!-- Notifications -->
+        <permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
+    </exception>
 </exceptions>
diff --git a/guest/commands/bt_vhci_forwarder/bt_vhci_forwarder.rc b/guest/commands/bt_vhci_forwarder/bt_vhci_forwarder.rc
new file mode 100644
index 0000000..8b7fb36
--- /dev/null
+++ b/guest/commands/bt_vhci_forwarder/bt_vhci_forwarder.rc
@@ -0,0 +1,7 @@
+on post-fs
+    start bt_vhci_forwarder
+
+service bt_vhci_forwarder /vendor/bin/bt_vhci_forwarder -virtio_console_dev=${vendor.ser.bt-uart}
+    user bluetooth
+    group bluetooth
+
diff --git a/guest/commands/bt_vhci_forwarder/main.cpp b/guest/commands/bt_vhci_forwarder/main.cpp
index d5c4622..2f1aab2 100644
--- a/guest/commands/bt_vhci_forwarder/main.cpp
+++ b/guest/commands/bt_vhci_forwarder/main.cpp
@@ -26,7 +26,7 @@
 
 #include "android-base/logging.h"
 
-#include "model/devices/h4_packetizer.h"
+#include "model/hci/h4_packetizer.h"
 
 // Copied from net/bluetooth/hci.h
 #define HCI_ACLDATA_PKT 0x02
@@ -109,7 +109,7 @@
   fds[1].events = POLLIN;
   unsigned char buf[kBufferSize];
 
-  auto h4 = test_vendor_lib::H4Packetizer(
+  auto h4 = rootcanal::H4Packetizer(
       virtio_fd,
       [](const std::vector<uint8_t>& /* raw_command */) {
         LOG(ERROR)
diff --git a/host/commands/mk_cdisk/Android.bp b/guest/commands/dlkm_loader/Android.bp
similarity index 72%
copy from host/commands/mk_cdisk/Android.bp
copy to guest/commands/dlkm_loader/Android.bp
index a0cf8ba..ae91c02 100644
--- a/host/commands/mk_cdisk/Android.bp
+++ b/guest/commands/dlkm_loader/Android.bp
@@ -18,24 +18,16 @@
 }
 
 cc_binary {
-    name: "mk_cdisk",
+    name: "dlkm_loader",
     srcs: [
-        "mk_cdisk.cc",
-    ],
-    shared_libs: [
-        "libcuttlefish_fs",
-        "libcuttlefish_utils",
-        "libbase",
-        "libjsoncpp",
-        "liblog",
-        "libz",
+        "dlkm_loader.cpp",
     ],
     static_libs: [
-        "libcdisk_spec",
-        "libext2_uuid",
-        "libimage_aggregator",
-        "libprotobuf-cpp-lite",
-        "libsparse",
+        "libbase",
+        "libmodprobe",
     ],
-    defaults: ["cuttlefish_host"],
+    shared_libs: [
+        "liblog",
+    ],
+    defaults: ["cuttlefish_guest_only"]
 }
diff --git a/guest/commands/dlkm_loader/dlkm_loader.cpp b/guest/commands/dlkm_loader/dlkm_loader.cpp
new file mode 100644
index 0000000..0d22225
--- /dev/null
+++ b/guest/commands/dlkm_loader/dlkm_loader.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <modprobe/modprobe.h>
+
+int main(void) {
+  LOG(INFO) << "dlkm loader successfully initialized";
+  Modprobe m({"/vendor/lib/modules"}, "modules.load");
+  CHECK(m.LoadListedModules(true)) << "modules from vendor dlkm weren't loaded correctly";
+  LOG(INFO) << "module load count is " << m.GetModuleCount();
+  return 0;
+}
diff --git a/guest/commands/rename_netiface/Android.bp b/guest/commands/rename_netiface/Android.bp
index f7b71be..3cf4225 100644
--- a/guest/commands/rename_netiface/Android.bp
+++ b/guest/commands/rename_netiface/Android.bp
@@ -23,6 +23,7 @@
     srcs: [
         "main.cpp",
     ],
+    init_rc: ["rename_netiface.rc"],
     shared_libs: [
         "cuttlefish_net",
     ],
diff --git a/guest/commands/rename_netiface/rename_netiface.rc b/guest/commands/rename_netiface/rename_netiface.rc
new file mode 100644
index 0000000..5117320
--- /dev/null
+++ b/guest/commands/rename_netiface/rename_netiface.rc
@@ -0,0 +1,2 @@
+service rename_eth0 /vendor/bin/rename_netiface eth0 buried_eth0
+    oneshot
diff --git a/guest/commands/sensor_injection/Android.bp b/guest/commands/sensor_injection/Android.bp
index a29250a..665b116 100644
--- a/guest/commands/sensor_injection/Android.bp
+++ b/guest/commands/sensor_injection/Android.bp
@@ -6,10 +6,9 @@
     name: "cuttlefish_sensor_injection",
     srcs: ["main.cpp"],
     shared_libs: [
-        "android.hardware.sensors@1.0",
-        "android.hardware.sensors@2.1",
+        "android.hardware.sensors-V1-ndk",
         "libbase",
-        "libbinder",
+        "libbinder_ndk",
         "libhidlbase",
         "liblog",
         "libutils",
diff --git a/guest/commands/sensor_injection/main.cpp b/guest/commands/sensor_injection/main.cpp
index 6eadb4a..a78de33 100644
--- a/guest/commands/sensor_injection/main.cpp
+++ b/guest/commands/sensor_injection/main.cpp
@@ -16,55 +16,52 @@
 
 #include <android-base/chrono_utils.h>
 #include <android-base/logging.h>
-#include <binder/IServiceManager.h>
-#include <utils/StrongPointer.h>
+#include <android/binder_manager.h>
 #include <utils/SystemClock.h>
 
 #include <thread>
 
-#include "android/hardware/sensors/2.1/ISensors.h"
+#include <aidl/android/hardware/sensors/BnSensors.h>
 
-using android::sp;
-using android::hardware::sensors::V1_0::OperationMode;
-using android::hardware::sensors::V1_0::Result;
-using android::hardware::sensors::V1_0::SensorStatus;
-using android::hardware::sensors::V2_1::Event;
-using android::hardware::sensors::V2_1::ISensors;
-using android::hardware::sensors::V2_1::SensorInfo;
-using android::hardware::sensors::V2_1::SensorType;
+using aidl::android::hardware::sensors::Event;
+using aidl::android::hardware::sensors::ISensors;
+using aidl::android::hardware::sensors::SensorInfo;
+using aidl::android::hardware::sensors::SensorStatus;
+using aidl::android::hardware::sensors::SensorType;
 
-sp<ISensors> startSensorInjection() {
-  const sp<ISensors> sensors = ISensors::getService();
+std::shared_ptr<ISensors> startSensorInjection() {
+  auto sensors = ISensors::fromBinder(ndk::SpAIBinder(
+      AServiceManager_getService("android.hardware.sensors.ISensors/default")));
   if (sensors == nullptr) {
     LOG(FATAL) << "Unable to get ISensors.";
   }
 
   // Place the ISensors HAL into DATA_INJECTION mode so that we can
   // inject events.
-  Result result = sensors->setOperationMode(OperationMode::DATA_INJECTION);
-  if (result != Result::OK) {
+  auto result =
+      sensors->setOperationMode(ISensors::OperationMode::DATA_INJECTION);
+  if (!result.isOk()) {
     LOG(FATAL) << "Unable to set ISensors operation mode to DATA_INJECTION: "
-               << toString(result);
+               << result.getDescription();
   }
 
   return sensors;
 }
 
-int getSensorHandle(SensorType type, const sp<ISensors> sensors) {
+int getSensorHandle(SensorType type, const std::shared_ptr<ISensors> sensors) {
   // Find the first available sensor of the given type.
   int handle = -1;
-  const auto& getSensorsList_result =
-      sensors->getSensorsList_2_1([&](const auto& list) {
-        for (const SensorInfo& sensor : list) {
-          if (sensor.type == type) {
-            handle = sensor.sensorHandle;
-            break;
-          }
-        }
-      });
-  if (!getSensorsList_result.isOk()) {
+  std::vector<SensorInfo> sensors_list;
+  auto result = sensors->getSensorsList(&sensors_list);
+  if (!result.isOk()) {
     LOG(FATAL) << "Unable to get ISensors sensors list: "
-               << getSensorsList_result.description();
+               << result.getDescription();
+  }
+  for (const SensorInfo& sensor : sensors_list) {
+    if (sensor.type == type) {
+      handle = sensor.sensorHandle;
+      break;
+    }
   }
   if (handle == -1) {
     LOG(FATAL) << "Unable to find sensor.";
@@ -72,45 +69,46 @@
   return handle;
 }
 
-void endSensorInjection(const sp<ISensors> sensors) {
+void endSensorInjection(const std::shared_ptr<ISensors> sensors) {
   // Return the ISensors HAL back to NORMAL mode.
-  Result result = sensors->setOperationMode(OperationMode::NORMAL);
-  if (result != Result::OK) {
+  auto result = sensors->setOperationMode(ISensors::OperationMode::NORMAL);
+  if (!result.isOk()) {
     LOG(FATAL) << "Unable to set sensors operation mode to NORMAL: "
-               << toString(result);
+               << result.getDescription();
   }
 }
 
 // Inject ACCELEROMETER events to corresponding to a given physical
 // device orientation: portrait or landscape.
 void InjectOrientation(bool portrait) {
-  sp<ISensors> sensors = startSensorInjection();
+  auto sensors = startSensorInjection();
   int handle = getSensorHandle(SensorType::ACCELEROMETER, sensors);
 
   // Create a base ISensors accelerometer event.
   Event event;
   event.sensorHandle = handle;
   event.sensorType = SensorType::ACCELEROMETER;
+  Event::EventPayload::Vec3 vec3;
   if (portrait) {
-    event.u.vec3.x = 0;
-    event.u.vec3.y = 9.2;
+    vec3.x = 0;
+    vec3.y = 9.2;
   } else {
-    event.u.vec3.x = 9.2;
-    event.u.vec3.y = 0;
+    vec3.x = 9.2;
+    vec3.y = 0;
   }
-  event.u.vec3.z = 3.5;
-  event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
+  vec3.z = 3.5;
+  vec3.status = SensorStatus::ACCURACY_HIGH;
+  event.payload.set<Event::EventPayload::Tag::vec3>(vec3);
 
   // Repeatedly inject accelerometer events. The WindowManager orientation
   // listener responds to sustained accelerometer data, not just a single event.
   android::base::Timer timer;
-  Result result;
   while (timer.duration() < 1s) {
     event.timestamp = android::elapsedRealtimeNano();
-    result = sensors->injectSensorData_2_1(event);
-    if (result != Result::OK) {
+    auto result = sensors->injectSensorData(event);
+    if (!result.isOk()) {
       LOG(FATAL) << "Unable to inject ISensors accelerometer event: "
-                 << toString(result);
+                 << result.getDescription();
     }
     std::this_thread::sleep_for(10ms);
   }
@@ -120,19 +118,20 @@
 
 // Inject a single HINGE_ANGLE event at the given angle.
 void InjectHingeAngle(int angle) {
-  sp<ISensors> sensors = startSensorInjection();
+  auto sensors = startSensorInjection();
   int handle = getSensorHandle(SensorType::HINGE_ANGLE, sensors);
 
   // Create a base ISensors hinge_angle event.
   Event event;
   event.sensorHandle = handle;
   event.sensorType = SensorType::HINGE_ANGLE;
-  event.u.scalar = angle;
-  event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
+  event.payload.set<Event::EventPayload::Tag::scalar>((float)angle);
   event.timestamp = android::elapsedRealtimeNano();
-  Result result = sensors->injectSensorData_2_1(event);
-  if (result != Result::OK) {
-    LOG(FATAL) << "Unable to inject HINGE_ANGLE data: " << toString(result);
+
+  auto result = sensors->injectSensorData(event);
+  if (!result.isOk()) {
+    LOG(FATAL) << "Unable to inject HINGE_ANGLE data"
+               << result.getDescription();
   }
 
   endSensorInjection(sensors);
diff --git a/guest/commands/setup_wifi/Android.bp b/guest/commands/setup_wifi/Android.bp
index 21472b1..e7a85bb 100644
--- a/guest/commands/setup_wifi/Android.bp
+++ b/guest/commands/setup_wifi/Android.bp
@@ -23,6 +23,7 @@
     srcs: [
         "main.cpp",
     ],
+    init_rc: ["setup_wifi.rc"],
     shared_libs: [
         "cuttlefish_net",
         "libbase",
diff --git a/guest/commands/setup_wifi/main.cpp b/guest/commands/setup_wifi/main.cpp
index 032f39e..4a91442 100644
--- a/guest/commands/setup_wifi/main.cpp
+++ b/guest/commands/setup_wifi/main.cpp
@@ -32,17 +32,15 @@
 #include "common/libs/net/network_interface.h"
 #include "common/libs/net/network_interface_manager.h"
 
-DEFINE_string(mac_address, "", "mac address to use for wlan0");
+DEFINE_string(mac_prefix, "", "mac prefix to use for wlan0");
 
-static std::array<unsigned char, 6> str_to_mac(const std::string& mac_str) {
+static std::array<unsigned char, 6> prefix_to_mac(
+    const std::string& mac_prefix) {
   std::array<unsigned char, 6> mac;
-  std::istringstream stream(mac_str);
-  for (int i = 0; i < 6; i++) {
-    int num;
-    stream >> std::hex >> num;
-    mac[i] = num;
-    stream.get();
-  }
+  int macPrefix = stoi(mac_prefix);
+  mac[0] = 0x02;
+  mac[1] = (macPrefix >> CHAR_BIT) & 0xFF;
+  mac[2] = macPrefix & 0xFF;
   return mac;
 }
 
@@ -51,7 +49,8 @@
   auto factory = cuttlefish::NetlinkClientFactory::Default();
   std::unique_ptr<cuttlefish::NetlinkClient> nl(factory->New(NETLINK_ROUTE));
 
-  LOG(INFO) << "Setting " << source << " mac address to " << FLAGS_mac_address;
+  LOG(INFO) << "Setting " << source << " mac address based on "
+            << FLAGS_mac_prefix;
   int32_t index = if_nametoindex(source.c_str());
   // Setting the address is available in RTM_SETLINK, but not RTM_NEWLINK.
   // https://elixir.bootlin.com/linux/v5.4.44/source/net/core/rtnetlink.c#L2785
@@ -64,7 +63,7 @@
     .ifi_index = index,
     .ifi_change = 0xFFFFFFFF,
   });
-  fix_mac_request.AddMacAddress(str_to_mac(FLAGS_mac_address));
+  fix_mac_request.AddMacAddress(prefix_to_mac(FLAGS_mac_prefix));
   bool fix_mac = nl->Send(fix_mac_request);
   if (!fix_mac) {
     LOG(ERROR) << "setup_network: could not fix mac address";
@@ -131,10 +130,10 @@
 }
 
 int main(int argc, char** argv) {
-  char wifi_address[PROPERTY_VALUE_MAX + 1];
-  property_get("ro.boot.wifi_mac_address", wifi_address, "");
+  char wifi_mac_prefix[PROPERTY_VALUE_MAX + 1];
+  property_get("ro.boot.wifi_mac_prefix", wifi_mac_prefix, "");
 
-  SetCommandLineOptionWithMode("mac_address", wifi_address,
+  SetCommandLineOptionWithMode("mac_prefix", wifi_mac_prefix,
                                google::FlagSettingMode::SET_FLAGS_DEFAULT);
 
   gflags::ParseCommandLineFlags(&argc, &argv, true);
diff --git a/guest/commands/setup_wifi/setup_wifi.rc b/guest/commands/setup_wifi/setup_wifi.rc
new file mode 100644
index 0000000..a2f1eb5
--- /dev/null
+++ b/guest/commands/setup_wifi/setup_wifi.rc
@@ -0,0 +1,2 @@
+service setup_wifi /vendor/bin/setup_wifi
+    oneshot
diff --git a/guest/hals/audio/audio_hw.c b/guest/hals/audio/audio_hw.c
deleted file mode 100644
index c0c705a..0000000
--- a/guest/hals/audio/audio_hw.c
+++ /dev/null
@@ -1,1854 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * This code was forked from device/generic/goldfish/audio/audio_hw.c
- *
- * At the time of forking, the code was identical except that a fallback
- * to a legacy HAL which does not use ALSA was removed, and the dependency
- * on libdl was also removed.
- */
-
-#define LOG_TAG "audio_hw_generic"
-
-#include <assert.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <pthread.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <sys/time.h>
-#include <dlfcn.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-#include <log/log.h>
-#include <cutils/list.h>
-#include <cutils/str_parms.h>
-
-#include <hardware/hardware.h>
-#include <system/audio.h>
-#include <hardware/audio.h>
-#include <tinyalsa/asoundlib.h>
-
-#define PCM_CARD 0
-#define PCM_DEVICE 0
-
-#define OUT_PERIOD_MS 10
-#define OUT_PERIOD_COUNT 4
-
-#define IN_PERIOD_MS 10
-#define IN_PERIOD_COUNT 4
-
-struct generic_audio_device {
-    struct audio_hw_device device;          // Constant after init
-    pthread_mutex_t lock;
-    bool mic_mute;                          // Protected by this->lock
-    struct mixer* mixer;                    // Protected by this->lock
-    struct listnode out_streams;            // Record for output streams, protected by this->lock
-    struct listnode in_streams;             // Record for input streams, protected by this->lock
-    audio_patch_handle_t next_patch_handle; // Protected by this->lock
-};
-
-/* If not NULL, this is a pointer to the fallback module.
- * This really is the original goldfish audio device /dev/eac which we will use
- * if no alsa devices are detected.
- */
-static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state);
-static int adev_get_microphones(const audio_hw_device_t *dev,
-                                struct audio_microphone_characteristic_t *mic_array,
-                                size_t *mic_count);
-
-
-typedef struct audio_vbuffer {
-    pthread_mutex_t lock;
-    uint8_t *  data;
-    size_t     frame_size;
-    size_t     frame_count;
-    size_t     head;
-    size_t     tail;
-    size_t     live;
-} audio_vbuffer_t;
-
-static int audio_vbuffer_init (audio_vbuffer_t * audio_vbuffer, size_t frame_count,
-                              size_t frame_size) {
-    if (!audio_vbuffer) {
-        return -EINVAL;
-    }
-    audio_vbuffer->frame_size = frame_size;
-    audio_vbuffer->frame_count = frame_count;
-    size_t bytes = frame_count * frame_size;
-    audio_vbuffer->data = calloc(bytes, 1);
-    if (!audio_vbuffer->data) {
-        return -ENOMEM;
-    }
-    audio_vbuffer->head = 0;
-    audio_vbuffer->tail = 0;
-    audio_vbuffer->live = 0;
-    pthread_mutex_init (&audio_vbuffer->lock, (const pthread_mutexattr_t *) NULL);
-    return 0;
-}
-
-static int audio_vbuffer_destroy (audio_vbuffer_t * audio_vbuffer) {
-    if (!audio_vbuffer) {
-        return -EINVAL;
-    }
-    free(audio_vbuffer->data);
-    pthread_mutex_destroy(&audio_vbuffer->lock);
-    return 0;
-}
-
-static int audio_vbuffer_live (audio_vbuffer_t * audio_vbuffer) {
-    if (!audio_vbuffer) {
-        return -EINVAL;
-    }
-    pthread_mutex_lock (&audio_vbuffer->lock);
-    int live = audio_vbuffer->live;
-    pthread_mutex_unlock (&audio_vbuffer->lock);
-    return live;
-}
-
-#define MIN(a,b) (((a)<(b))?(a):(b))
-static size_t audio_vbuffer_write (audio_vbuffer_t * audio_vbuffer, const void * buffer, size_t frame_count) {
-    size_t frames_written = 0;
-    pthread_mutex_lock (&audio_vbuffer->lock);
-
-    while (frame_count != 0) {
-        int frames = 0;
-        if (audio_vbuffer->live == 0 || audio_vbuffer->head > audio_vbuffer->tail) {
-            frames = MIN(frame_count, audio_vbuffer->frame_count - audio_vbuffer->head);
-        } else if (audio_vbuffer->head < audio_vbuffer->tail) {
-            frames = MIN(frame_count, audio_vbuffer->tail - (audio_vbuffer->head));
-        } else {
-            // Full
-            break;
-        }
-        memcpy(&audio_vbuffer->data[audio_vbuffer->head*audio_vbuffer->frame_size],
-               &((uint8_t*)buffer)[frames_written*audio_vbuffer->frame_size],
-               frames*audio_vbuffer->frame_size);
-        audio_vbuffer->live += frames;
-        frames_written += frames;
-        frame_count -= frames;
-        audio_vbuffer->head = (audio_vbuffer->head + frames) % audio_vbuffer->frame_count;
-    }
-
-    pthread_mutex_unlock (&audio_vbuffer->lock);
-    return frames_written;
-}
-
-static size_t audio_vbuffer_read (audio_vbuffer_t * audio_vbuffer, void * buffer, size_t frame_count) {
-    size_t frames_read = 0;
-    pthread_mutex_lock (&audio_vbuffer->lock);
-
-    while (frame_count != 0) {
-        int frames = 0;
-        if (audio_vbuffer->live == audio_vbuffer->frame_count ||
-            audio_vbuffer->tail > audio_vbuffer->head) {
-            frames = MIN(frame_count, audio_vbuffer->frame_count - audio_vbuffer->tail);
-        } else if (audio_vbuffer->tail < audio_vbuffer->head) {
-            frames = MIN(frame_count, audio_vbuffer->head - audio_vbuffer->tail);
-        } else {
-            break;
-        }
-        memcpy(&((uint8_t*)buffer)[frames_read*audio_vbuffer->frame_size],
-               &audio_vbuffer->data[audio_vbuffer->tail*audio_vbuffer->frame_size],
-               frames*audio_vbuffer->frame_size);
-        audio_vbuffer->live -= frames;
-        frames_read += frames;
-        frame_count -= frames;
-        audio_vbuffer->tail = (audio_vbuffer->tail + frames) % audio_vbuffer->frame_count;
-    }
-
-    pthread_mutex_unlock (&audio_vbuffer->lock);
-    return frames_read;
-}
-
-struct generic_stream_out {
-    struct audio_stream_out stream;                 // Constant after init
-    pthread_mutex_t lock;
-    struct generic_audio_device *dev;               // Constant after init
-    uint32_t num_devices;                           // Protected by this->lock
-    audio_devices_t devices[AUDIO_PATCH_PORTS_MAX]; // Protected by this->lock
-    struct audio_config req_config;                 // Constant after init
-    struct pcm_config pcm_config;                   // Constant after init
-    audio_vbuffer_t buffer;                         // Constant after init
-
-    // Time & Position Keeping
-    bool standby;                      // Protected by this->lock
-    uint64_t underrun_position;        // Protected by this->lock
-    struct timespec underrun_time;     // Protected by this->lock
-    uint64_t last_write_time_us;       // Protected by this->lock
-    uint64_t frames_total_buffered;    // Protected by this->lock
-    uint64_t frames_written;           // Protected by this->lock
-    uint64_t frames_rendered;          // Protected by this->lock
-
-    // Worker
-    pthread_t worker_thread;          // Constant after init
-    pthread_cond_t worker_wake;       // Protected by this->lock
-    bool worker_standby;              // Protected by this->lock
-    bool worker_exit;                 // Protected by this->lock
-
-    audio_io_handle_t handle;          // Constant after init
-    audio_patch_handle_t patch_handle; // Protected by this->dev->lock
-
-    struct listnode stream_node;       // Protected by this->dev->lock
-};
-
-struct generic_stream_in {
-    struct audio_stream_in stream;    // Constant after init
-    pthread_mutex_t lock;
-    struct generic_audio_device *dev; // Constant after init
-    audio_devices_t device;           // Protected by this->lock
-    struct audio_config req_config;   // Constant after init
-    struct pcm *pcm;                  // Protected by this->lock
-    struct pcm_config pcm_config;     // Constant after init
-    int16_t *stereo_to_mono_buf;      // Protected by this->lock
-    size_t stereo_to_mono_buf_size;   // Protected by this->lock
-    audio_vbuffer_t buffer;           // Protected by this->lock
-
-    // Time & Position Keeping
-    bool standby;                     // Protected by this->lock
-    int64_t standby_position;         // Protected by this->lock
-    struct timespec standby_exit_time;// Protected by this->lock
-    int64_t standby_frames_read;      // Protected by this->lock
-
-    // Worker
-    pthread_t worker_thread;          // Constant after init
-    pthread_cond_t worker_wake;       // Protected by this->lock
-    bool worker_standby;              // Protected by this->lock
-    bool worker_exit;                 // Protected by this->lock
-
-    audio_io_handle_t handle;          // Constant after init
-    audio_patch_handle_t patch_handle; // Protected by this->dev->lock
-
-    struct listnode stream_node;       // Protected by this->dev->lock
-};
-
-static struct pcm_config pcm_config_out = {
-    .channels = 2,
-    .rate = 0,
-    .period_size = 0,
-    .period_count = OUT_PERIOD_COUNT,
-    .format = PCM_FORMAT_S16_LE,
-    .start_threshold = 0,
-};
-
-static struct pcm_config pcm_config_in = {
-    .channels = 2,
-    .rate = 0,
-    .period_size = 0,
-    .period_count = IN_PERIOD_COUNT,
-    .format = PCM_FORMAT_S16_LE,
-    .start_threshold = 0,
-    .stop_threshold = INT_MAX,
-};
-
-static pthread_mutex_t adev_init_lock = PTHREAD_MUTEX_INITIALIZER;
-static unsigned int audio_device_ref_count = 0;
-
-static uint32_t out_get_sample_rate(const struct audio_stream *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    return out->req_config.sample_rate;
-}
-
-static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate)
-{
-    return -ENOSYS;
-}
-
-static size_t out_get_buffer_size(const struct audio_stream *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    int size = out->pcm_config.period_size *
-                audio_stream_out_frame_size(&out->stream);
-
-    return size;
-}
-
-static audio_channel_mask_t out_get_channels(const struct audio_stream *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    return out->req_config.channel_mask;
-}
-
-static audio_format_t out_get_format(const struct audio_stream *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-
-    return out->req_config.format;
-}
-
-static int out_set_format(struct audio_stream *stream, audio_format_t format)
-{
-    return -ENOSYS;
-}
-
-static int out_dump(const struct audio_stream *stream, int fd)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    pthread_mutex_lock(&out->lock);
-    dprintf(fd, "\tout_dump:\n"
-                "\t\tsample rate: %u\n"
-                "\t\tbuffer size: %zu\n"
-                "\t\tchannel mask: %08x\n"
-                "\t\tformat: %d\n"
-                "\t\tdevice(s): ",
-                out_get_sample_rate(stream),
-                out_get_buffer_size(stream),
-                out_get_channels(stream),
-                out_get_format(stream));
-    if (out->num_devices == 0) {
-        dprintf(fd, "%08x\n", AUDIO_DEVICE_NONE);
-    } else {
-        for (uint32_t i = 0; i < out->num_devices; i++) {
-            if (i != 0) {
-                dprintf(fd, ", ");
-            }
-            dprintf(fd, "%08x", out->devices[i]);
-        }
-        dprintf(fd, "\n");
-    }
-    dprintf(fd, "\t\taudio dev: %p\n\n", out->dev);
-    pthread_mutex_unlock(&out->lock);
-    return 0;
-}
-
-static int out_set_parameters(struct audio_stream *stream, const char *kvpairs)
-{
-    struct str_parms *parms;
-    char value[32];
-    int success;
-    int ret = -EINVAL;
-
-    if (kvpairs == NULL || kvpairs[0] == 0) {
-        return 0;
-    }
-    parms = str_parms_create_str(kvpairs);
-    success = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING,
-            value, sizeof(value));
-    // As the hal version is 3.0, it must not use set parameters API to set audio devices.
-    // Instead, it should use create_audio_patch API.
-    assert(("Must not use set parameters API to set audio devices", success < 0));
-
-    if (str_parms_has_key(parms, AUDIO_PARAMETER_STREAM_FORMAT)) {
-        // match the return value of out_set_format
-        ret = -ENOSYS;
-    }
-
-    str_parms_destroy(parms);
-
-    if (ret == -EINVAL) {
-        ALOGW("%s(), unsupported parameter %s", __func__, kvpairs);
-        // There is not any key supported for set_parameters API.
-        // Return error when there is non-null value passed in.
-    }
-    return ret;
-}
-
-static char * out_get_parameters(const struct audio_stream *stream, const char *keys)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    struct str_parms *query = str_parms_create_str(keys);
-    char *str = NULL;
-    char value[256];
-    struct str_parms *reply = str_parms_create();
-    int ret;
-    bool get = false;
-
-    ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value));
-    if (ret >= 0) {
-        pthread_mutex_lock(&out->lock);
-        audio_devices_t device = AUDIO_DEVICE_NONE;
-        for (uint32_t i = 0; i < out->num_devices; i++) {
-            device |= out->devices[i];
-        }
-        str_parms_add_int(reply, AUDIO_PARAMETER_STREAM_ROUTING, device);
-        pthread_mutex_unlock(&out->lock);
-        get = true;
-    }
-
-    if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_SUP_FORMATS)) {
-        value[0] = 0;
-        strcat(value, "AUDIO_FORMAT_PCM_16_BIT");
-        str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_SUP_FORMATS, value);
-        get = true;
-    }
-
-    if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_FORMAT)) {
-        value[0] = 0;
-        strcat(value, "AUDIO_FORMAT_PCM_16_BIT");
-        str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_FORMAT, value);
-        get = true;
-    }
-
-    if (get) {
-        str = str_parms_to_str(reply);
-    }
-    else {
-        ALOGD("%s Unsupported paramter: %s", __FUNCTION__, keys);
-    }
-
-    str_parms_destroy(query);
-    str_parms_destroy(reply);
-    return str;
-}
-
-static uint32_t out_get_latency(const struct audio_stream_out *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    return (out->pcm_config.period_size * 1000) / out->pcm_config.rate;
-}
-
-static int out_set_volume(struct audio_stream_out *stream, float left,
-                          float right)
-{
-    return -ENOSYS;
-}
-
-static void *out_write_worker(void * args)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)args;
-    struct pcm *pcm = NULL;
-    uint8_t *buffer = NULL;
-    int buffer_frames;
-    int buffer_size;
-    bool restart = false;
-    bool shutdown = false;
-    while (true) {
-        pthread_mutex_lock(&out->lock);
-        while (out->worker_standby || restart) {
-            restart = false;
-            if (pcm) {
-                pcm_close(pcm); // Frees pcm
-                pcm = NULL;
-                free(buffer);
-                buffer=NULL;
-            }
-            if (out->worker_exit) {
-                break;
-            }
-            pthread_cond_wait(&out->worker_wake, &out->lock);
-        }
-
-        if (out->worker_exit) {
-            if (!out->worker_standby) {
-                ALOGE("Out worker not in standby before exiting");
-            }
-            shutdown = true;
-        }
-
-        while (!shutdown && audio_vbuffer_live(&out->buffer) == 0) {
-            pthread_cond_wait(&out->worker_wake, &out->lock);
-        }
-
-        if (shutdown) {
-            pthread_mutex_unlock(&out->lock);
-            break;
-        }
-
-        if (!pcm) {
-            pcm = pcm_open(PCM_CARD, PCM_DEVICE,
-                          PCM_OUT | PCM_MONOTONIC, &out->pcm_config);
-            if (!pcm_is_ready(pcm)) {
-                ALOGE("pcm_open(out) failed: %s: channels %d format %d rate %d",
-                  pcm_get_error(pcm),
-                  out->pcm_config.channels,
-                  out->pcm_config.format,
-                  out->pcm_config.rate
-                   );
-                pthread_mutex_unlock(&out->lock);
-                break;
-            }
-            buffer_frames = out->pcm_config.period_size;
-            buffer_size = pcm_frames_to_bytes(pcm, buffer_frames);
-            buffer = malloc(buffer_size);
-            if (!buffer) {
-                ALOGE("could not allocate write buffer");
-                pthread_mutex_unlock(&out->lock);
-                break;
-            }
-        }
-        int frames = audio_vbuffer_read(&out->buffer, buffer, buffer_frames);
-        pthread_mutex_unlock(&out->lock);
-        int ret = pcm_write(pcm, buffer, pcm_frames_to_bytes(pcm, frames));
-        if (ret != 0) {
-            ALOGE("pcm_write failed %s", pcm_get_error(pcm));
-            restart = true;
-        }
-    }
-    if (buffer) {
-        free(buffer);
-    }
-
-    return NULL;
-}
-
-// Call with in->lock held
-static void get_current_output_position(struct generic_stream_out *out,
-                                       uint64_t * position,
-                                       struct timespec * timestamp) {
-    struct timespec curtime = { .tv_sec = 0, .tv_nsec = 0 };
-    clock_gettime(CLOCK_MONOTONIC, &curtime);
-    const int64_t now_us = (curtime.tv_sec * 1000000000LL + curtime.tv_nsec) / 1000;
-    if (timestamp) {
-        *timestamp = curtime;
-    }
-    int64_t position_since_underrun;
-    if (out->standby) {
-        position_since_underrun = 0;
-    } else {
-        const int64_t first_us = (out->underrun_time.tv_sec * 1000000000LL +
-                                  out->underrun_time.tv_nsec) / 1000;
-        position_since_underrun = (now_us - first_us) *
-                out_get_sample_rate(&out->stream.common) /
-                1000000;
-        if (position_since_underrun < 0) {
-            position_since_underrun = 0;
-        }
-    }
-    *position = out->underrun_position + position_since_underrun;
-
-    // The device will reuse the same output stream leading to periods of
-    // underrun.
-    if (*position > out->frames_written) {
-        ALOGW("Not supplying enough data to HAL, expected position %" PRIu64 " , only wrote "
-              "%" PRIu64,
-              *position, out->frames_written);
-
-        *position = out->frames_written;
-        out->underrun_position = *position;
-        out->underrun_time = curtime;
-        out->frames_total_buffered = 0;
-    }
-}
-
-
-static ssize_t out_write(struct audio_stream_out *stream, const void *buffer,
-                         size_t bytes)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    const size_t frames =  bytes / audio_stream_out_frame_size(stream);
-
-    pthread_mutex_lock(&out->lock);
-
-    if (out->worker_standby) {
-        out->worker_standby = false;
-    }
-
-    uint64_t current_position;
-    struct timespec current_time;
-
-    get_current_output_position(out, &current_position, &current_time);
-    const uint64_t now_us = (current_time.tv_sec * 1000000000LL +
-                             current_time.tv_nsec) / 1000;
-    if (out->standby) {
-        out->standby = false;
-        out->underrun_time = current_time;
-        out->frames_rendered = 0;
-        out->frames_total_buffered = 0;
-    }
-
-    size_t frames_written = audio_vbuffer_write(&out->buffer, buffer, frames);
-    pthread_cond_signal(&out->worker_wake);
-
-    /* Implementation just consumes bytes if we start getting backed up */
-    out->frames_written += frames;
-    out->frames_rendered += frames;
-    out->frames_total_buffered += frames;
-
-    // We simulate the audio device blocking when it's write buffers become
-    // full.
-
-    // At the beginning or after an underrun, try to fill up the vbuffer.
-    // This will be throttled by the PlaybackThread
-    int frames_sleep = out->frames_total_buffered < out->buffer.frame_count ? 0 : frames;
-
-    uint64_t sleep_time_us = frames_sleep * 1000000LL /
-                            out_get_sample_rate(&stream->common);
-
-    // If the write calls are delayed, subtract time off of the sleep to
-    // compensate
-    uint64_t time_since_last_write_us = now_us - out->last_write_time_us;
-    if (time_since_last_write_us < sleep_time_us) {
-        sleep_time_us -= time_since_last_write_us;
-    } else {
-        sleep_time_us = 0;
-    }
-    out->last_write_time_us = now_us + sleep_time_us;
-
-    pthread_mutex_unlock(&out->lock);
-
-    if (sleep_time_us > 0) {
-        usleep(sleep_time_us);
-    }
-
-    if (frames_written < frames) {
-        ALOGW("Hardware backing HAL too slow, could only write %zu of %zu frames", frames_written, frames);
-    }
-
-    /* Always consume all bytes */
-    return bytes;
-}
-
-static int out_get_presentation_position(const struct audio_stream_out *stream,
-                                   uint64_t *frames, struct timespec *timestamp)
-
-{
-    if (stream == NULL || frames == NULL || timestamp == NULL) {
-        return -EINVAL;
-    }
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-
-    pthread_mutex_lock(&out->lock);
-    get_current_output_position(out, frames, timestamp);
-    pthread_mutex_unlock(&out->lock);
-
-    return 0;
-}
-
-static int out_get_render_position(const struct audio_stream_out *stream,
-                                   uint32_t *dsp_frames)
-{
-    if (stream == NULL || dsp_frames == NULL) {
-        return -EINVAL;
-    }
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    pthread_mutex_lock(&out->lock);
-    *dsp_frames = out->frames_rendered;
-    pthread_mutex_unlock(&out->lock);
-    return 0;
-}
-
-// Must be called with out->lock held
-static void do_out_standby(struct generic_stream_out *out)
-{
-    int frames_sleep = 0;
-    uint64_t sleep_time_us = 0;
-    if (out->standby) {
-        return;
-    }
-    while (true) {
-        get_current_output_position(out, &out->underrun_position, NULL);
-        frames_sleep = out->frames_written - out->underrun_position;
-
-        if (frames_sleep == 0) {
-            break;
-        }
-
-        sleep_time_us = frames_sleep * 1000000LL /
-                        out_get_sample_rate(&out->stream.common);
-
-        pthread_mutex_unlock(&out->lock);
-        usleep(sleep_time_us);
-        pthread_mutex_lock(&out->lock);
-    }
-    out->worker_standby = true;
-    out->standby = true;
-}
-
-static int out_standby(struct audio_stream *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    pthread_mutex_lock(&out->lock);
-    do_out_standby(out);
-    pthread_mutex_unlock(&out->lock);
-    return 0;
-}
-
-static int out_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
-{
-    // out_add_audio_effect is a no op
-    return 0;
-}
-
-static int out_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
-{
-    // out_remove_audio_effect is a no op
-    return 0;
-}
-
-static int out_get_next_write_timestamp(const struct audio_stream_out *stream,
-                                        int64_t *timestamp)
-{
-    return -ENOSYS;
-}
-
-static uint32_t in_get_sample_rate(const struct audio_stream *stream)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    return in->req_config.sample_rate;
-}
-
-static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate)
-{
-    return -ENOSYS;
-}
-
-static int refine_output_parameters(uint32_t *sample_rate, audio_format_t *format, audio_channel_mask_t *channel_mask)
-{
-    static const uint32_t sample_rates [] = {8000,11025,16000,22050,24000,32000,
-                                            44100,48000};
-    static const int sample_rates_count = sizeof(sample_rates)/sizeof(uint32_t);
-    bool inval = false;
-    if (*format != AUDIO_FORMAT_PCM_16_BIT) {
-        *format = AUDIO_FORMAT_PCM_16_BIT;
-        inval = true;
-    }
-
-    int channel_count = popcount(*channel_mask);
-    if (channel_count != 1 && channel_count != 2) {
-        *channel_mask = AUDIO_CHANNEL_IN_STEREO;
-        inval = true;
-    }
-
-    int i;
-    for (i = 0; i < sample_rates_count; i++) {
-        if (*sample_rate < sample_rates[i]) {
-            *sample_rate = sample_rates[i];
-            inval=true;
-            break;
-        }
-        else if (*sample_rate == sample_rates[i]) {
-            break;
-        }
-        else if (i == sample_rates_count-1) {
-            // Cap it to the highest rate we support
-            *sample_rate = sample_rates[i];
-            inval=true;
-        }
-    }
-
-    if (inval) {
-        return -EINVAL;
-    }
-    return 0;
-}
-
-static int refine_input_parameters(uint32_t *sample_rate, audio_format_t *format, audio_channel_mask_t *channel_mask)
-{
-    static const uint32_t sample_rates [] = {8000, 11025, 16000, 22050, 44100, 48000};
-    static const int sample_rates_count = sizeof(sample_rates)/sizeof(uint32_t);
-    bool inval = false;
-    // Only PCM_16_bit is supported. If this is changed, stereo to mono drop
-    // must be fixed in in_read
-    if (*format != AUDIO_FORMAT_PCM_16_BIT) {
-        *format = AUDIO_FORMAT_PCM_16_BIT;
-        inval = true;
-    }
-
-    int channel_count = popcount(*channel_mask);
-    if (channel_count != 1 && channel_count != 2) {
-        *channel_mask = AUDIO_CHANNEL_IN_STEREO;
-        inval = true;
-    }
-
-    int i;
-    for (i = 0; i < sample_rates_count; i++) {
-        if (*sample_rate < sample_rates[i]) {
-            *sample_rate = sample_rates[i];
-            inval=true;
-            break;
-        }
-        else if (*sample_rate == sample_rates[i]) {
-            break;
-        }
-        else if (i == sample_rates_count-1) {
-            // Cap it to the highest rate we support
-            *sample_rate = sample_rates[i];
-            inval=true;
-        }
-    }
-
-    if (inval) {
-        return -EINVAL;
-    }
-    return 0;
-}
-
-static int check_input_parameters(uint32_t sample_rate, audio_format_t format,
-                                  audio_channel_mask_t channel_mask)
-{
-    return refine_input_parameters(&sample_rate, &format, &channel_mask);
-}
-
-static size_t get_input_buffer_size(uint32_t sample_rate, audio_format_t format,
-                                    audio_channel_mask_t channel_mask)
-{
-    size_t size;
-    int channel_count = popcount(channel_mask);
-    if (check_input_parameters(sample_rate, format, channel_mask) != 0)
-        return 0;
-
-    size = sample_rate*IN_PERIOD_MS/1000;
-    // Audioflinger expects audio buffers to be multiple of 16 frames
-    size = ((size + 15) / 16) * 16;
-    size *= sizeof(short) * channel_count;
-
-    return size;
-}
-
-
-static size_t in_get_buffer_size(const struct audio_stream *stream)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    int size = get_input_buffer_size(in->req_config.sample_rate,
-                                 in->req_config.format,
-                                 in->req_config.channel_mask);
-
-    return size;
-}
-
-static audio_channel_mask_t in_get_channels(const struct audio_stream *stream)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    return in->req_config.channel_mask;
-}
-
-static audio_format_t in_get_format(const struct audio_stream *stream)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    return in->req_config.format;
-}
-
-static int in_set_format(struct audio_stream *stream, audio_format_t format)
-{
-    return -ENOSYS;
-}
-
-static int in_dump(const struct audio_stream *stream, int fd)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-
-    pthread_mutex_lock(&in->lock);
-    dprintf(fd, "\tin_dump:\n"
-                "\t\tsample rate: %u\n"
-                "\t\tbuffer size: %zu\n"
-                "\t\tchannel mask: %08x\n"
-                "\t\tformat: %d\n"
-                "\t\tdevice: %08x\n"
-                "\t\taudio dev: %p\n\n",
-                in_get_sample_rate(stream),
-                in_get_buffer_size(stream),
-                in_get_channels(stream),
-                in_get_format(stream),
-                in->device,
-                in->dev);
-    pthread_mutex_unlock(&in->lock);
-    return 0;
-}
-
-static int in_set_parameters(struct audio_stream *stream, const char *kvpairs)
-{
-    struct str_parms *parms;
-    char value[32];
-    int success;
-    int ret = -EINVAL;
-
-    if (kvpairs == NULL || kvpairs[0] == 0) {
-        return 0;
-    }
-    parms = str_parms_create_str(kvpairs);
-    success = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING,
-            value, sizeof(value));
-    // As the hal version is 3.0, it must not use set parameters API to set audio device.
-    // Instead, it should use create_audio_patch API.
-    assert(("Must not use set parameters API to set audio devices", success < 0));
-
-    if (str_parms_has_key(parms, AUDIO_PARAMETER_STREAM_FORMAT)) {
-        // match the return value of in_set_format
-        ret = -ENOSYS;
-    }
-
-    str_parms_destroy(parms);
-
-    if (ret == -EINVAL) {
-        ALOGW("%s(), unsupported parameter %s", __func__, kvpairs);
-        // There is not any key supported for set_parameters API.
-        // Return error when there is non-null value passed in.
-    }
-    return ret;
-}
-
-static char * in_get_parameters(const struct audio_stream *stream,
-                                const char *keys)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    struct str_parms *query = str_parms_create_str(keys);
-    char *str = NULL;
-    char value[256];
-    struct str_parms *reply = str_parms_create();
-    int ret;
-    bool get = false;
-
-    ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value));
-    if (ret >= 0) {
-        str_parms_add_int(reply, AUDIO_PARAMETER_STREAM_ROUTING, in->device);
-        get = true;
-    }
-
-    if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_SUP_FORMATS)) {
-        value[0] = 0;
-        strcat(value, "AUDIO_FORMAT_PCM_16_BIT");
-        str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_SUP_FORMATS, value);
-        get = true;
-    }
-
-    if (str_parms_has_key(query, AUDIO_PARAMETER_STREAM_FORMAT)) {
-        value[0] = 0;
-        strcat(value, "AUDIO_FORMAT_PCM_16_BIT");
-        str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_FORMAT, value);
-        get = true;
-    }
-
-    if (get) {
-        str = str_parms_to_str(reply);
-    }
-    else {
-        ALOGD("%s Unsupported paramter: %s", __FUNCTION__, keys);
-    }
-
-    str_parms_destroy(query);
-    str_parms_destroy(reply);
-    return str;
-}
-
-static int in_set_gain(struct audio_stream_in *stream, float gain)
-{
-    // in_set_gain is a no op
-    return 0;
-}
-
-// Call with in->lock held
-static void get_current_input_position(struct generic_stream_in *in,
-                                       int64_t * position,
-                                       struct timespec * timestamp) {
-    struct timespec t = { .tv_sec = 0, .tv_nsec = 0 };
-    clock_gettime(CLOCK_MONOTONIC, &t);
-    const int64_t now_us = (t.tv_sec * 1000000000LL + t.tv_nsec) / 1000;
-    if (timestamp) {
-        *timestamp = t;
-    }
-    int64_t position_since_standby;
-    if (in->standby) {
-        position_since_standby = 0;
-    } else {
-        const int64_t first_us = (in->standby_exit_time.tv_sec * 1000000000LL +
-                                  in->standby_exit_time.tv_nsec) / 1000;
-        position_since_standby = (now_us - first_us) *
-                in_get_sample_rate(&in->stream.common) /
-                1000000;
-        if (position_since_standby < 0) {
-            position_since_standby = 0;
-        }
-    }
-    *position = in->standby_position + position_since_standby;
-}
-
-// Must be called with in->lock held
-static void do_in_standby(struct generic_stream_in *in)
-{
-    if (in->standby) {
-        return;
-    }
-    in->worker_standby = true;
-    get_current_input_position(in, &in->standby_position, NULL);
-    in->standby = true;
-}
-
-static int in_standby(struct audio_stream *stream)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    pthread_mutex_lock(&in->lock);
-    do_in_standby(in);
-    pthread_mutex_unlock(&in->lock);
-    return 0;
-}
-
-static void *in_read_worker(void * args)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)args;
-    struct pcm *pcm = NULL;
-    uint8_t *buffer = NULL;
-    size_t buffer_frames;
-    int buffer_size;
-
-    bool restart = false;
-    bool shutdown = false;
-    while (true) {
-        pthread_mutex_lock(&in->lock);
-        while (in->worker_standby || restart) {
-            restart = false;
-            if (pcm) {
-                pcm_close(pcm); // Frees pcm
-                pcm = NULL;
-                free(buffer);
-                buffer=NULL;
-            }
-            if (in->worker_exit) {
-                break;
-            }
-            pthread_cond_wait(&in->worker_wake, &in->lock);
-        }
-
-        if (in->worker_exit) {
-            if (!in->worker_standby) {
-                ALOGE("In worker not in standby before exiting");
-            }
-            shutdown = true;
-        }
-        if (shutdown) {
-            pthread_mutex_unlock(&in->lock);
-            break;
-        }
-        if (!pcm) {
-            pcm = pcm_open(PCM_CARD, PCM_DEVICE,
-                          PCM_IN | PCM_MONOTONIC, &in->pcm_config);
-            if (!pcm_is_ready(pcm)) {
-                ALOGE("pcm_open(in) failed: %s: channels %d format %d rate %d",
-                  pcm_get_error(pcm),
-                  in->pcm_config.channels,
-                  in->pcm_config.format,
-                  in->pcm_config.rate
-                   );
-                pthread_mutex_unlock(&in->lock);
-                break;
-            }
-            buffer_frames = in->pcm_config.period_size;
-            buffer_size = pcm_frames_to_bytes(pcm, buffer_frames);
-            buffer = malloc(buffer_size);
-            if (!buffer) {
-                ALOGE("could not allocate worker read buffer");
-                pthread_mutex_unlock(&in->lock);
-                break;
-            }
-        }
-        pthread_mutex_unlock(&in->lock);
-        int ret = pcm_read(pcm, buffer, pcm_frames_to_bytes(pcm, buffer_frames));
-        if (ret != 0) {
-            ALOGW("pcm_read failed %s", pcm_get_error(pcm));
-            restart = true;
-            continue;
-        }
-
-        pthread_mutex_lock(&in->lock);
-        size_t frames_written = audio_vbuffer_write(&in->buffer, buffer, buffer_frames);
-        pthread_mutex_unlock(&in->lock);
-
-        if (frames_written != buffer_frames) {
-            ALOGW("in_read_worker only could write %zu / %zu frames", frames_written, buffer_frames);
-        }
-    }
-    if (buffer) {
-        free(buffer);
-    }
-    return NULL;
-}
-
-static ssize_t in_read(struct audio_stream_in *stream, void* buffer,
-                       size_t bytes)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    struct generic_audio_device *adev = in->dev;
-    const size_t frames =  bytes / audio_stream_in_frame_size(stream);
-    bool mic_mute = false;
-    size_t read_bytes = 0;
-
-    adev_get_mic_mute(&adev->device, &mic_mute);
-    pthread_mutex_lock(&in->lock);
-
-    if (in->worker_standby) {
-        in->worker_standby = false;
-    }
-    pthread_cond_signal(&in->worker_wake);
-
-    int64_t current_position;
-    struct timespec current_time;
-
-    get_current_input_position(in, &current_position, &current_time);
-    if (in->standby) {
-        in->standby = false;
-        in->standby_exit_time = current_time;
-        in->standby_frames_read = 0;
-    }
-
-    const int64_t frames_available = current_position - in->standby_position - in->standby_frames_read;
-    assert(frames_available >= 0);
-
-    const size_t frames_wait = ((uint64_t)frames_available > frames) ? 0 : frames - frames_available;
-
-    int64_t sleep_time_us  = frames_wait * 1000000LL /
-                             in_get_sample_rate(&stream->common);
-
-    pthread_mutex_unlock(&in->lock);
-
-    if (sleep_time_us > 0) {
-        usleep(sleep_time_us);
-    }
-
-    pthread_mutex_lock(&in->lock);
-    int read_frames = 0;
-    if (in->standby) {
-        ALOGW("Input put to sleep while read in progress");
-        goto exit;
-    }
-    in->standby_frames_read += frames;
-
-    if (popcount(in->req_config.channel_mask) == 1 &&
-        in->pcm_config.channels == 2) {
-        // Need to resample to mono
-        if (in->stereo_to_mono_buf_size < bytes*2) {
-            in->stereo_to_mono_buf = realloc(in->stereo_to_mono_buf,
-                                             bytes*2);
-            if (!in->stereo_to_mono_buf) {
-                ALOGE("Failed to allocate stereo_to_mono_buff");
-                goto exit;
-            }
-        }
-
-        read_frames = audio_vbuffer_read(&in->buffer, in->stereo_to_mono_buf, frames);
-
-        // Currently only pcm 16 is supported.
-        uint16_t *src = (uint16_t *)in->stereo_to_mono_buf;
-        uint16_t *dst = (uint16_t *)buffer;
-        size_t i;
-        // Resample stereo 16 to mono 16 by dropping one channel.
-        // The stereo stream is interleaved L-R-L-R
-        for (i = 0; i < frames; i++) {
-            *dst = *src;
-            src += 2;
-            dst += 1;
-        }
-    } else {
-        read_frames = audio_vbuffer_read(&in->buffer, buffer, frames);
-    }
-
-exit:
-    read_bytes = read_frames*audio_stream_in_frame_size(stream);
-
-    if (mic_mute) {
-        read_bytes = 0;
-    }
-
-    if (read_bytes < bytes) {
-        memset (&((uint8_t *)buffer)[read_bytes], 0, bytes-read_bytes);
-    }
-
-    pthread_mutex_unlock(&in->lock);
-
-    return bytes;
-}
-
-static uint32_t in_get_input_frames_lost(struct audio_stream_in *stream)
-{
-    return 0;
-}
-
-static int in_get_capture_position(const struct audio_stream_in *stream,
-                                int64_t *frames, int64_t *time)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    pthread_mutex_lock(&in->lock);
-    struct timespec current_time;
-    get_current_input_position(in, frames, &current_time);
-    *time = (current_time.tv_sec * 1000000000LL + current_time.tv_nsec);
-    pthread_mutex_unlock(&in->lock);
-    return 0;
-}
-
-static int in_get_active_microphones(const struct audio_stream_in *stream,
-                                     struct audio_microphone_characteristic_t *mic_array,
-                                     size_t *mic_count)
-{
-    return adev_get_microphones(NULL, mic_array, mic_count);
-}
-
-static int in_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
-{
-    // in_add_audio_effect is a no op
-    return 0;
-}
-
-static int in_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
-{
-    // in_add_audio_effect is a no op
-    return 0;
-}
-
-static int adev_open_output_stream(struct audio_hw_device *dev,
-                                   audio_io_handle_t handle,
-                                   audio_devices_t devices,
-                                   audio_output_flags_t flags,
-                                   struct audio_config *config,
-                                   struct audio_stream_out **stream_out,
-                                   const char *address __unused)
-{
-    struct generic_audio_device *adev = (struct generic_audio_device *)dev;
-    struct generic_stream_out *out;
-    int ret = 0;
-
-    if (refine_output_parameters(&config->sample_rate, &config->format, &config->channel_mask)) {
-        ALOGE("Error opening output stream format %d, channel_mask %04x, sample_rate %u",
-              config->format, config->channel_mask, config->sample_rate);
-        ret = -EINVAL;
-        goto error;
-    }
-
-    out = (struct generic_stream_out *)calloc(1, sizeof(struct generic_stream_out));
-
-    if (!out)
-        return -ENOMEM;
-
-    out->stream.common.get_sample_rate = out_get_sample_rate;
-    out->stream.common.set_sample_rate = out_set_sample_rate;
-    out->stream.common.get_buffer_size = out_get_buffer_size;
-    out->stream.common.get_channels = out_get_channels;
-    out->stream.common.get_format = out_get_format;
-    out->stream.common.set_format = out_set_format;
-    out->stream.common.standby = out_standby;
-    out->stream.common.dump = out_dump;
-    out->stream.common.set_parameters = out_set_parameters;
-    out->stream.common.get_parameters = out_get_parameters;
-    out->stream.common.add_audio_effect = out_add_audio_effect;
-    out->stream.common.remove_audio_effect = out_remove_audio_effect;
-    out->stream.get_latency = out_get_latency;
-    out->stream.set_volume = out_set_volume;
-    out->stream.write = out_write;
-    out->stream.get_render_position = out_get_render_position;
-    out->stream.get_presentation_position = out_get_presentation_position;
-    out->stream.get_next_write_timestamp = out_get_next_write_timestamp;
-
-    out->handle = handle;
-
-    pthread_mutex_init(&out->lock, (const pthread_mutexattr_t *) NULL);
-    out->dev = adev;
-    // Only 1 device is expected despite the argument being named 'devices'
-    out->num_devices = 1;
-    out->devices[0] = devices;
-    memcpy(&out->req_config, config, sizeof(struct audio_config));
-    memcpy(&out->pcm_config, &pcm_config_out, sizeof(struct pcm_config));
-    out->pcm_config.rate = config->sample_rate;
-    out->pcm_config.period_size = out->pcm_config.rate*OUT_PERIOD_MS/1000;
-
-    out->standby = true;
-    out->underrun_position = 0;
-    out->underrun_time.tv_sec = 0;
-    out->underrun_time.tv_nsec = 0;
-    out->last_write_time_us = 0;
-    out->frames_total_buffered = 0;
-    out->frames_written = 0;
-    out->frames_rendered = 0;
-
-    ret = audio_vbuffer_init(&out->buffer,
-                      out->pcm_config.period_size*out->pcm_config.period_count,
-                      out->pcm_config.channels *
-                      pcm_format_to_bits(out->pcm_config.format) >> 3);
-    if (ret == 0) {
-        pthread_cond_init(&out->worker_wake, NULL);
-        out->worker_standby = true;
-        out->worker_exit = false;
-        pthread_create(&out->worker_thread, NULL, out_write_worker, out);
-
-    }
-
-    pthread_mutex_lock(&adev->lock);
-    list_add_tail(&adev->out_streams, &out->stream_node);
-    pthread_mutex_unlock(&adev->lock);
-
-    *stream_out = &out->stream;
-
-error:
-
-    return ret;
-}
-
-// This must be called with adev->lock held.
-struct generic_stream_out *get_stream_out_by_io_handle_l(
-        struct generic_audio_device *adev, audio_io_handle_t handle) {
-    struct listnode *node;
-
-    list_for_each(node, &adev->out_streams) {
-        struct generic_stream_out *out = node_to_item(
-                node, struct generic_stream_out, stream_node);
-        if (out->handle == handle) {
-            return out;
-        }
-    }
-    return NULL;
-}
-
-static void adev_close_output_stream(struct audio_hw_device *dev,
-                                     struct audio_stream_out *stream)
-{
-    struct generic_stream_out *out = (struct generic_stream_out *)stream;
-    pthread_mutex_lock(&out->lock);
-    do_out_standby(out);
-
-    out->worker_exit = true;
-    pthread_cond_signal(&out->worker_wake);
-    pthread_mutex_unlock(&out->lock);
-
-    pthread_join(out->worker_thread, NULL);
-    pthread_mutex_destroy(&out->lock);
-    audio_vbuffer_destroy(&out->buffer);
-
-    struct generic_audio_device *adev = (struct generic_audio_device *) dev;
-    pthread_mutex_lock(&adev->lock);
-    list_remove(&out->stream_node);
-    pthread_mutex_unlock(&adev->lock);
-    free(stream);
-}
-
-static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs)
-{
-    return 0;
-}
-
-static char * adev_get_parameters(const struct audio_hw_device *dev,
-                                  const char *keys)
-{
-    return strdup("");
-}
-
-static int adev_get_audio_port(struct audio_hw_device *dev,
-                               struct audio_port *port)
-{
-    return 0;
-}
-
-static int adev_init_check(const struct audio_hw_device *dev)
-{
-    return 0;
-}
-
-static int adev_set_voice_volume(struct audio_hw_device *dev, float volume)
-{
-    // adev_set_voice_volume is a no op (simulates phones)
-    return 0;
-}
-
-static int adev_set_audio_port_config(struct audio_hw_device *dev,
-                                      const struct audio_port_config *config) {
-  return 0;
-}
-
-static int adev_set_master_volume(struct audio_hw_device *dev, float volume)
-{
-    return -ENOSYS;
-}
-
-static int adev_get_master_volume(struct audio_hw_device *dev, float *volume)
-{
-    return -ENOSYS;
-}
-
-static int adev_set_master_mute(struct audio_hw_device *dev, bool muted)
-{
-    return -ENOSYS;
-}
-
-static int adev_get_master_mute(struct audio_hw_device *dev, bool *muted)
-{
-    return -ENOSYS;
-}
-
-static int adev_set_mode(struct audio_hw_device *dev, audio_mode_t mode)
-{
-    // adev_set_mode is a no op (simulates phones)
-    return 0;
-}
-
-static int adev_set_mic_mute(struct audio_hw_device *dev, bool state)
-{
-    struct generic_audio_device *adev = (struct generic_audio_device *)dev;
-    pthread_mutex_lock(&adev->lock);
-    adev->mic_mute = state;
-    pthread_mutex_unlock(&adev->lock);
-    return 0;
-}
-
-static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state)
-{
-    struct generic_audio_device *adev = (struct generic_audio_device *)dev;
-    pthread_mutex_lock(&adev->lock);
-    *state = adev->mic_mute;
-    pthread_mutex_unlock(&adev->lock);
-    return 0;
-}
-
-
-static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev,
-                                         const struct audio_config *config)
-{
-    return get_input_buffer_size(config->sample_rate, config->format, config->channel_mask);
-}
-
-// This must be called with adev->lock held.
-struct generic_stream_in *get_stream_in_by_io_handle_l(
-        struct generic_audio_device *adev, audio_io_handle_t handle) {
-    struct listnode *node;
-
-    list_for_each(node, &adev->in_streams) {
-        struct generic_stream_in *in = node_to_item(
-                node, struct generic_stream_in, stream_node);
-        if (in->handle == handle) {
-            return in;
-        }
-    }
-    return NULL;
-}
-
-static void adev_close_input_stream(struct audio_hw_device *dev,
-                                   struct audio_stream_in *stream)
-{
-    struct generic_stream_in *in = (struct generic_stream_in *)stream;
-    pthread_mutex_lock(&in->lock);
-    do_in_standby(in);
-
-    in->worker_exit = true;
-    pthread_cond_signal(&in->worker_wake);
-    pthread_mutex_unlock(&in->lock);
-    pthread_join(in->worker_thread, NULL);
-
-    if (in->stereo_to_mono_buf != NULL) {
-        free(in->stereo_to_mono_buf);
-        in->stereo_to_mono_buf_size = 0;
-    }
-
-    pthread_mutex_destroy(&in->lock);
-    audio_vbuffer_destroy(&in->buffer);
-
-    struct generic_audio_device *adev = (struct generic_audio_device *) dev;
-    pthread_mutex_lock(&adev->lock);
-    list_remove(&in->stream_node);
-    pthread_mutex_unlock(&adev->lock);
-    free(stream);
-}
-
-
-static int adev_open_input_stream(struct audio_hw_device *dev,
-                                  audio_io_handle_t handle,
-                                  audio_devices_t devices,
-                                  struct audio_config *config,
-                                  struct audio_stream_in **stream_in,
-                                  audio_input_flags_t flags __unused,
-                                  const char *address __unused,
-                                  audio_source_t source __unused)
-{
-    struct generic_audio_device *adev = (struct generic_audio_device *)dev;
-    struct generic_stream_in *in;
-    int ret = 0;
-    uint32_t orig_sample_rate = config->sample_rate;
-    audio_format_t orig_audio_format = config->format;
-    audio_channel_mask_t orig_channel_mask = config->channel_mask;
-    if (refine_input_parameters(&config->sample_rate, &config->format, &config->channel_mask)) {
-        ALOGE("Error opening input stream format %d, channel_mask %04x, sample_rate %u",
-              orig_audio_format, orig_channel_mask, orig_sample_rate);
-        ret = -EINVAL;
-        goto error;
-    }
-
-    in = (struct generic_stream_in *)calloc(1, sizeof(struct generic_stream_in));
-    if (!in) {
-        ret = -ENOMEM;
-        goto error;
-    }
-
-    in->stream.common.get_sample_rate = in_get_sample_rate;
-    in->stream.common.set_sample_rate = in_set_sample_rate;         // no op
-    in->stream.common.get_buffer_size = in_get_buffer_size;
-    in->stream.common.get_channels = in_get_channels;
-    in->stream.common.get_format = in_get_format;
-    in->stream.common.set_format = in_set_format;                   // no op
-    in->stream.common.standby = in_standby;
-    in->stream.common.dump = in_dump;
-    in->stream.common.set_parameters = in_set_parameters;
-    in->stream.common.get_parameters = in_get_parameters;
-    in->stream.common.add_audio_effect = in_add_audio_effect;       // no op
-    in->stream.common.remove_audio_effect = in_remove_audio_effect; // no op
-    in->stream.set_gain = in_set_gain;                              // no op
-    in->stream.read = in_read;
-    in->stream.get_input_frames_lost = in_get_input_frames_lost;    // no op
-    in->stream.get_capture_position = in_get_capture_position;
-    in->stream.get_active_microphones = in_get_active_microphones;
-
-    pthread_mutex_init(&in->lock, (const pthread_mutexattr_t *) NULL);
-    in->dev = adev;
-    in->device = devices;
-    memcpy(&in->req_config, config, sizeof(struct audio_config));
-    memcpy(&in->pcm_config, &pcm_config_in, sizeof(struct pcm_config));
-    in->pcm_config.rate = config->sample_rate;
-    in->pcm_config.period_size = in->pcm_config.rate*IN_PERIOD_MS/1000;
-
-    in->stereo_to_mono_buf = NULL;
-    in->stereo_to_mono_buf_size = 0;
-
-    in->standby = true;
-    in->standby_position = 0;
-    in->standby_exit_time.tv_sec = 0;
-    in->standby_exit_time.tv_nsec = 0;
-    in->standby_frames_read = 0;
-
-    ret = audio_vbuffer_init(&in->buffer,
-                      in->pcm_config.period_size*in->pcm_config.period_count,
-                      in->pcm_config.channels *
-                      pcm_format_to_bits(in->pcm_config.format) >> 3);
-    if (ret == 0) {
-        pthread_cond_init(&in->worker_wake, NULL);
-        in->worker_standby = true;
-        in->worker_exit = false;
-        pthread_create(&in->worker_thread, NULL, in_read_worker, in);
-    }
-    in->handle = handle;
-
-    pthread_mutex_lock(&adev->lock);
-    list_add_tail(&adev->in_streams, &in->stream_node);
-    pthread_mutex_unlock(&adev->lock);
-
-    *stream_in = &in->stream;
-
-error:
-    return ret;
-}
-
-
-static int adev_dump(const audio_hw_device_t *dev, int fd)
-{
-    return 0;
-}
-
-static int adev_get_microphones(const audio_hw_device_t *dev,
-                                struct audio_microphone_characteristic_t *mic_array,
-                                size_t *mic_count)
-{
-    if (mic_count == NULL) {
-        return -ENOSYS;
-    }
-
-    if (*mic_count == 0) {
-        *mic_count = 1;
-        return 0;
-    }
-
-    if (mic_array == NULL) {
-        return -ENOSYS;
-    }
-
-    strncpy(mic_array->device_id, "mic_goldfish", AUDIO_MICROPHONE_ID_MAX_LEN - 1);
-    mic_array->device = AUDIO_DEVICE_IN_BUILTIN_MIC;
-    strncpy(mic_array->address, AUDIO_BOTTOM_MICROPHONE_ADDRESS,
-            AUDIO_DEVICE_MAX_ADDRESS_LEN - 1);
-    memset(mic_array->channel_mapping, AUDIO_MICROPHONE_CHANNEL_MAPPING_UNUSED,
-           sizeof(mic_array->channel_mapping));
-    mic_array->location = AUDIO_MICROPHONE_LOCATION_UNKNOWN;
-    mic_array->group = 0;
-    mic_array->index_in_the_group = 0;
-    mic_array->sensitivity = AUDIO_MICROPHONE_SENSITIVITY_UNKNOWN;
-    mic_array->max_spl = AUDIO_MICROPHONE_SPL_UNKNOWN;
-    mic_array->min_spl = AUDIO_MICROPHONE_SPL_UNKNOWN;
-    mic_array->directionality = AUDIO_MICROPHONE_DIRECTIONALITY_UNKNOWN;
-    mic_array->num_frequency_responses = 0;
-    mic_array->geometric_location.x = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-    mic_array->geometric_location.y = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-    mic_array->geometric_location.z = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-    mic_array->orientation.x = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-    mic_array->orientation.y = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-    mic_array->orientation.z = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
-
-    *mic_count = 1;
-    return 0;
-}
-
-static int adev_create_audio_patch(struct audio_hw_device *dev,
-                                   unsigned int num_sources,
-                                   const struct audio_port_config *sources,
-                                   unsigned int num_sinks,
-                                   const struct audio_port_config *sinks,
-                                   audio_patch_handle_t *handle) {
-    if (num_sources != 1 || num_sinks == 0 || num_sinks > AUDIO_PATCH_PORTS_MAX) {
-        return -EINVAL;
-    }
-
-    if (sources[0].type == AUDIO_PORT_TYPE_DEVICE) {
-        // If source is a device, the number of sinks should be 1.
-        if (num_sinks != 1 || sinks[0].type != AUDIO_PORT_TYPE_MIX) {
-            return -EINVAL;
-        }
-    } else if (sources[0].type == AUDIO_PORT_TYPE_MIX) {
-        // If source is a mix, all sinks should be device.
-        for (unsigned int i = 0; i < num_sinks; i++) {
-            if (sinks[i].type != AUDIO_PORT_TYPE_DEVICE) {
-                ALOGE("%s() invalid sink type %#x for mix source", __func__, sinks[i].type);
-                return -EINVAL;
-            }
-        }
-    } else {
-        // All other cases are invalid.
-        return -EINVAL;
-    }
-
-    struct generic_audio_device* adev = (struct generic_audio_device*) dev;
-    int ret = 0;
-    bool generatedPatchHandle = false;
-    pthread_mutex_lock(&adev->lock);
-    if (*handle == AUDIO_PATCH_HANDLE_NONE) {
-        *handle = ++adev->next_patch_handle;
-        generatedPatchHandle = true;
-    }
-
-    // Only handle patches for mix->devices and device->mix case.
-    if (sources[0].type == AUDIO_PORT_TYPE_DEVICE) {
-        struct generic_stream_in *in =
-                get_stream_in_by_io_handle_l(adev, sinks[0].ext.mix.handle);
-        if (in == NULL) {
-            ALOGE("%s()can not find stream with handle(%d)", __func__, sources[0].ext.mix.handle);
-            ret = -EINVAL;
-            goto error;
-        }
-
-        // Check if the patch handle match the recorded one if a valid patch handle is passed.
-        if (!generatedPatchHandle && in->patch_handle != *handle) {
-            ALOGE("%s() the patch handle(%d) does not match recorded one(%d) for stream "
-                  "with handle(%d) when creating audio patch for device->mix",
-                  __func__, *handle, in->patch_handle, in->handle);
-            ret = -EINVAL;
-            goto error;
-        }
-        pthread_mutex_lock(&in->lock);
-        in->device = sources[0].ext.device.type;
-        pthread_mutex_unlock(&in->lock);
-        in->patch_handle = *handle;
-    } else {
-        struct generic_stream_out *out =
-                get_stream_out_by_io_handle_l(adev, sources[0].ext.mix.handle);
-        if (out == NULL) {
-            ALOGE("%s()can not find stream with handle(%d)", __func__, sources[0].ext.mix.handle);
-            ret = -EINVAL;
-            goto error;
-        }
-
-        // Check if the patch handle match the recorded one if a valid patch handle is passed.
-        if (!generatedPatchHandle && out->patch_handle != *handle) {
-            ALOGE("%s() the patch handle(%d) does not match recorded one(%d) for stream "
-                  "with handle(%d) when creating audio patch for mix->device",
-                  __func__, *handle, out->patch_handle, out->handle);
-            ret = -EINVAL;
-            pthread_mutex_unlock(&out->lock);
-            goto error;
-        }
-        pthread_mutex_lock(&out->lock);
-        for (out->num_devices = 0; out->num_devices < num_sinks; out->num_devices++) {
-            out->devices[out->num_devices] = sinks[out->num_devices].ext.device.type;
-        }
-        pthread_mutex_unlock(&out->lock);
-        out->patch_handle = *handle;
-    }
-
-error:
-    if (ret != 0 && generatedPatchHandle) {
-        *handle = AUDIO_PATCH_HANDLE_NONE;
-    }
-    pthread_mutex_unlock(&adev->lock);
-    return 0;
-}
-
-// This must be called with adev->lock held.
-struct generic_stream_out *get_stream_out_by_patch_handle_l(
-        struct generic_audio_device *adev, audio_patch_handle_t patch_handle) {
-    struct listnode *node;
-
-    list_for_each(node, &adev->out_streams) {
-        struct generic_stream_out *out = node_to_item(
-                node, struct generic_stream_out, stream_node);
-        if (out->patch_handle == patch_handle) {
-            return out;
-        }
-    }
-    return NULL;
-}
-
-// This must be called with adev->lock held.
-struct generic_stream_in *get_stream_in_by_patch_handle_l(
-        struct generic_audio_device *adev, audio_patch_handle_t patch_handle) {
-    struct listnode *node;
-
-    list_for_each(node, &adev->in_streams) {
-        struct generic_stream_in *in = node_to_item(
-                node, struct generic_stream_in, stream_node);
-        if (in->patch_handle == patch_handle) {
-            return in;
-        }
-    }
-    return NULL;
-}
-
-static int adev_release_audio_patch(struct audio_hw_device *dev,
-                                    audio_patch_handle_t patch_handle) {
-    struct generic_audio_device *adev = (struct generic_audio_device *) dev;
-
-    pthread_mutex_lock(&adev->lock);
-    struct generic_stream_out *out = get_stream_out_by_patch_handle_l(adev, patch_handle);
-    if (out != NULL) {
-        pthread_mutex_lock(&out->lock);
-        out->num_devices = 0;
-        memset(out->devices, 0, sizeof(out->devices));
-        pthread_mutex_unlock(&out->lock);
-        out->patch_handle = AUDIO_PATCH_HANDLE_NONE;
-        pthread_mutex_unlock(&adev->lock);
-        return 0;
-    }
-    struct generic_stream_in *in = get_stream_in_by_patch_handle_l(adev, patch_handle);
-    if (in != NULL) {
-        pthread_mutex_lock(&in->lock);
-        in->device = AUDIO_DEVICE_NONE;
-        pthread_mutex_unlock(&in->lock);
-        in->patch_handle = AUDIO_PATCH_HANDLE_NONE;
-        pthread_mutex_unlock(&adev->lock);
-        return 0;
-    }
-
-    pthread_mutex_unlock(&adev->lock);
-    ALOGW("%s() cannot find stream for patch handle: %d", __func__, patch_handle);
-    return -EINVAL;
-}
-
-static int adev_close(hw_device_t *dev)
-{
-    struct generic_audio_device *adev = (struct generic_audio_device *)dev;
-    int ret = 0;
-    if (!adev)
-        return 0;
-
-    pthread_mutex_lock(&adev_init_lock);
-
-    if (audio_device_ref_count == 0) {
-        ALOGE("adev_close called when ref_count 0");
-        ret = -EINVAL;
-        goto error;
-    }
-
-    if ((--audio_device_ref_count) == 0) {
-        if (adev->mixer) {
-            mixer_close(adev->mixer);
-        }
-        free(adev);
-    }
-
-error:
-    pthread_mutex_unlock(&adev_init_lock);
-    return ret;
-}
-
-static int adev_open(const hw_module_t* module, const char* name,
-                     hw_device_t** device)
-{
-    static struct generic_audio_device *adev;
-
-    if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0)
-        return -EINVAL;
-
-    pthread_mutex_lock(&adev_init_lock);
-    if (audio_device_ref_count != 0) {
-        *device = &adev->device.common;
-        audio_device_ref_count++;
-        ALOGV("%s: returning existing instance of adev", __func__);
-        ALOGV("%s: exit", __func__);
-        goto unlock;
-    }
-    adev = calloc(1, sizeof(struct generic_audio_device));
-
-    pthread_mutex_init(&adev->lock, (const pthread_mutexattr_t *) NULL);
-
-    adev->device.common.tag = HARDWARE_DEVICE_TAG;
-    adev->device.common.version = AUDIO_DEVICE_API_VERSION_3_0;
-    adev->device.common.module = (struct hw_module_t *) module;
-    adev->device.common.close = adev_close;
-
-    adev->device.init_check = adev_init_check;               // no op
-    adev->device.set_voice_volume = adev_set_voice_volume;   // no op
-    adev->device.set_master_volume = adev_set_master_volume; // no op
-    adev->device.get_master_volume = adev_get_master_volume; // no op
-    adev->device.set_master_mute = adev_set_master_mute;     // no op
-    adev->device.get_master_mute = adev_get_master_mute;     // no op
-    adev->device.set_mode = adev_set_mode;                   // no op
-    adev->device.set_mic_mute = adev_set_mic_mute;
-    adev->device.get_mic_mute = adev_get_mic_mute;
-    adev->device.set_parameters = adev_set_parameters;       // no op
-    adev->device.get_parameters = adev_get_parameters;       // no op
-    adev->device.get_audio_port = adev_get_audio_port;       // no op
-    adev->device.set_audio_port_config = adev_set_audio_port_config;  // no op
-    adev->device.get_input_buffer_size = adev_get_input_buffer_size;
-    adev->device.open_output_stream = adev_open_output_stream;
-    adev->device.close_output_stream = adev_close_output_stream;
-    adev->device.open_input_stream = adev_open_input_stream;
-    adev->device.close_input_stream = adev_close_input_stream;
-    adev->device.dump = adev_dump;
-    adev->device.get_microphones = adev_get_microphones;
-    adev->device.create_audio_patch = adev_create_audio_patch;
-    adev->device.release_audio_patch = adev_release_audio_patch;
-
-    *device = &adev->device.common;
-
-    adev->next_patch_handle = AUDIO_PATCH_HANDLE_NONE;
-    list_init(&adev->out_streams);
-    list_init(&adev->in_streams);
-
-    adev->mixer = mixer_open(PCM_CARD);
-    struct mixer_ctl *ctl;
-
-    // Set default mixer ctls
-    // Enable channels and set volume
-    for (int i = 0; i < (int)mixer_get_num_ctls(adev->mixer); i++) {
-        ctl = mixer_get_ctl(adev->mixer, i);
-        ALOGD("mixer %d name %s", i, mixer_ctl_get_name(ctl));
-        if (!strcmp(mixer_ctl_get_name(ctl), "Master Playback Volume") ||
-            !strcmp(mixer_ctl_get_name(ctl), "Capture Volume")) {
-            for (int z = 0; z < (int)mixer_ctl_get_num_values(ctl); z++) {
-                ALOGD("set ctl %d to %d", z, 100);
-                mixer_ctl_set_percent(ctl, z, 100);
-            }
-            continue;
-        }
-        if (!strcmp(mixer_ctl_get_name(ctl), "Master Playback Switch") ||
-            !strcmp(mixer_ctl_get_name(ctl), "Capture Switch")) {
-            for (int z = 0; z < (int)mixer_ctl_get_num_values(ctl); z++) {
-                ALOGD("set ctl %d to %d", z, 1);
-                mixer_ctl_set_value(ctl, z, 1);
-            }
-            continue;
-        }
-    }
-
-    audio_device_ref_count++;
-
-unlock:
-    pthread_mutex_unlock(&adev_init_lock);
-    return 0;
-}
-
-static struct hw_module_methods_t hal_module_methods = {
-    .open = adev_open,
-};
-
-struct audio_module HAL_MODULE_INFO_SYM = {
-    .common = {
-        .tag = HARDWARE_MODULE_TAG,
-        .module_api_version = AUDIO_MODULE_API_VERSION_0_1,
-        .hal_api_version = HARDWARE_HAL_API_VERSION,
-        .id = AUDIO_HARDWARE_MODULE_ID,
-        .name = "Generic audio HW HAL",
-        .author = "The Android Open Source Project",
-        .methods = &hal_module_methods,
-    },
-};
diff --git a/guest/hals/bt/OWNERS b/guest/hals/bt/OWNERS
index e8a4a00..b4bb5dd 100644
--- a/guest/hals/bt/OWNERS
+++ b/guest/hals/bt/OWNERS
@@ -1,2 +1,3 @@
-include platform/system/bt:/OWNERS
+include device/google/cuttlefish:/OWNERS
+include platform/packages/modules/Bluetooth:/OWNERS
 jeongik@google.com
\ No newline at end of file
diff --git a/guest/hals/bt/remote/Android.bp b/guest/hals/bt/remote/Android.bp
index 5ad483a..512bee5 100644
--- a/guest/hals/bt/remote/Android.bp
+++ b/guest/hals/bt/remote/Android.bp
@@ -25,7 +25,6 @@
     ],
     static_libs: [
         "libbt-rootcanal",
-        "libbt-rootcanal-types",
         "async_fd_watcher",
     ],
     init_rc: ["android.hardware.bluetooth@1.1-service.remote.rc"],
diff --git a/guest/hals/bt/remote/remote_bluetooth.cpp b/guest/hals/bt/remote/remote_bluetooth.cpp
index dbcede3..d041af7 100644
--- a/guest/hals/bt/remote/remote_bluetooth.cpp
+++ b/guest/hals/bt/remote/remote_bluetooth.cpp
@@ -125,7 +125,7 @@
     CHECK(death_recipient_->getHasDied())
         << "Error sending init callback, but no death notification.";
   }
-  h4_ = test_vendor_lib::H4Packetizer(
+  h4_ = rootcanal::H4Packetizer(
       fd_,
       [](const std::vector<uint8_t>& /* raw_command */) {
         LOG_ALWAYS_FATAL("Unexpected command!");
@@ -159,26 +159,26 @@
 }
 
 Return<void> BluetoothHci::sendHciCommand(const hidl_vec<uint8_t>& packet) {
-  send(test_vendor_lib::PacketType::COMMAND, packet);
+  send(rootcanal::PacketType::COMMAND, packet);
   return Void();
 }
 
 Return<void> BluetoothHci::sendAclData(const hidl_vec<uint8_t>& packet) {
-  send(test_vendor_lib::PacketType::ACL, packet);
+  send(rootcanal::PacketType::ACL, packet);
   return Void();
 }
 
 Return<void> BluetoothHci::sendScoData(const hidl_vec<uint8_t>& packet) {
-  send(test_vendor_lib::PacketType::SCO, packet);
+  send(rootcanal::PacketType::SCO, packet);
   return Void();
 }
 
 Return<void> BluetoothHci::sendIsoData(const hidl_vec<uint8_t>& packet) {
-  send(test_vendor_lib::PacketType::ISO, packet);
+  send(rootcanal::PacketType::ISO, packet);
   return Void();
 }
 
-void BluetoothHci::send(test_vendor_lib::PacketType type,
+void BluetoothHci::send(rootcanal::PacketType type,
                         const ::android::hardware::hidl_vec<uint8_t>& v) {
   h4_.Send(static_cast<uint8_t>(type), v.data(), v.size());
 }
diff --git a/guest/hals/bt/remote/remote_bluetooth.h b/guest/hals/bt/remote/remote_bluetooth.h
index a7aa24c..ade39d0 100644
--- a/guest/hals/bt/remote/remote_bluetooth.h
+++ b/guest/hals/bt/remote/remote_bluetooth.h
@@ -22,7 +22,7 @@
 #include <hidl/MQDescriptor.h>
 #include <string>
 #include "async_fd_watcher.h"
-#include "model/devices/h4_packetizer.h"
+#include "model/hci/h4_packetizer.h"
 
 namespace android {
 namespace hardware {
@@ -68,13 +68,13 @@
   ::android::sp<V1_0::IBluetoothHciCallbacks> cb_ = nullptr;
   ::android::sp<V1_1::IBluetoothHciCallbacks> cb_1_1_ = nullptr;
 
-  test_vendor_lib::H4Packetizer h4_{fd_,
-                                    [](const std::vector<uint8_t>&) {},
-                                    [](const std::vector<uint8_t>&) {},
-                                    [](const std::vector<uint8_t>&) {},
-                                    [](const std::vector<uint8_t>&) {},
-                                    [](const std::vector<uint8_t>&) {},
-                                    [] {}};
+  rootcanal::H4Packetizer h4_{fd_,
+                              [](const std::vector<uint8_t>&) {},
+                              [](const std::vector<uint8_t>&) {},
+                              [](const std::vector<uint8_t>&) {},
+                              [](const std::vector<uint8_t>&) {},
+                              [](const std::vector<uint8_t>&) {},
+                              [] {}};
 
   ::android::hardware::Return<void> initialize_impl(
       const sp<V1_0::IBluetoothHciCallbacks>& cb,
@@ -88,7 +88,7 @@
 
   ::android::hardware::bluetooth::async::AsyncFdWatcher fd_watcher_;
 
-  void send(test_vendor_lib::PacketType type,
+  void send(rootcanal::PacketType type,
             const ::android::hardware::hidl_vec<uint8_t>& packet);
 };
 
diff --git a/guest/hals/camera/vsock_frame_provider.cpp b/guest/hals/camera/vsock_frame_provider.cpp
index 435d5f9..baa4c52 100644
--- a/guest/hals/camera/vsock_frame_provider.cpp
+++ b/guest/hals/camera/vsock_frame_provider.cpp
@@ -22,6 +22,16 @@
 
 namespace cuttlefish {
 
+namespace {
+bool writeJsonEventMessage(
+    std::shared_ptr<cuttlefish::VsockConnection> connection,
+    const std::string& message) {
+  Json::Value json_message;
+  json_message["event"] = message;
+  return connection && connection->WriteMessage(json_message);
+}
+}  // namespace
+
 VsockFrameProvider::~VsockFrameProvider() { stop(); }
 
 void VsockFrameProvider::start(
@@ -30,6 +40,7 @@
   stop();
   running_ = true;
   connection_ = connection;
+  writeJsonEventMessage(connection, "VIRTUAL_DEVICE_START_CAMERA_SESSION");
   reader_thread_ =
       std::thread([this, width, height] { VsockReadLoop(width, height); });
 }
@@ -40,6 +51,7 @@
   if (reader_thread_.joinable()) {
     reader_thread_.join();
   }
+  writeJsonEventMessage(connection_, "VIRTUAL_DEVICE_STOP_CAMERA_SESSION");
   connection_ = nullptr;
 }
 
@@ -53,11 +65,7 @@
 
 void VsockFrameProvider::requestJpeg() {
   jpeg_pending_ = true;
-  Json::Value message;
-  message["event"] = "VIRTUAL_DEVICE_CAPTURE_IMAGE";
-  if (connection_) {
-    connection_->WriteMessage(message);
-  }
+  writeJsonEventMessage(connection_, "VIRTUAL_DEVICE_CAPTURE_IMAGE");
 }
 
 void VsockFrameProvider::cancelJpegRequest() { jpeg_pending_ = false; }
diff --git a/guest/hals/confirmationui/.clang-format b/guest/hals/confirmationui/.clang-format
new file mode 100644
index 0000000..b0dc94c
--- /dev/null
+++ b/guest/hals/confirmationui/.clang-format
@@ -0,0 +1,10 @@
+BasedOnStyle: LLVM
+IndentWidth: 4
+UseTab: Never
+BreakBeforeBraces: Attach
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: true
+IndentCaseLabels: false
+ColumnLimit: 100
+PointerBindsToType: true
+SpacesBeforeTrailingComments: 2
diff --git a/guest/hals/confirmationui/Android.bp b/guest/hals/confirmationui/Android.bp
new file mode 100644
index 0000000..cb9318a
--- /dev/null
+++ b/guest/hals/confirmationui/Android.bp
@@ -0,0 +1,89 @@
+// Copyright (C) 2021 The Android Open-Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// WARNING: Everything listed here will be built on ALL platforms,
+// including x86, the emulator, and the SDK.  Modules must be uniquely
+// named (liblights.panda), and must build everywhere, or limit themselves
+// to only building on ARM if they include assembly. Individual makefiles
+// are responsible for having their own logic, for fine-grained control.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "android.hardware.confirmationui@1.0-service.cuttlefish",
+    defaults: ["hidl_defaults", "cuttlefish_guest_only"],
+    relative_install_path: "hw",
+    vendor: true,
+    shared_libs: [
+        "android.hardware.confirmationui@1.0",
+        "android.hardware.confirmationui@1.0-lib.cuttlefish",
+        "libbase",
+        "libhidlbase",
+        "libutils",
+    ],
+    static_libs: [
+        "libcuttlefish_confui",
+    ],
+
+    init_rc: ["android.hardware.confirmationui@1.0-service.cuttlefish.rc"],
+
+    vintf_fragments: ["android.hardware.confirmationui@1.0-service.cuttlefish.xml"],
+
+    srcs: [
+        "service.cpp",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-DTEEUI_USE_STD_VECTOR",
+    ],
+}
+
+cc_library {
+    name: "android.hardware.confirmationui@1.0-lib.cuttlefish",
+    defaults: ["hidl_defaults", "cuttlefish_guest_only"],
+    vendor: true,
+    shared_libs: [
+        "android.hardware.confirmationui@1.0",
+        "android.hardware.keymaster@4.0",
+        "libbase",
+        "libcutils",
+        "libdmabufheap",
+        "libhidlbase",
+        "libteeui_hal_support",
+        "libtrusty",
+        "libutils",
+    ],
+
+    export_include_dirs: ["include"],
+
+    srcs: [
+        "TrustyConfirmationUI.cpp",
+        "guest_session.cpp",
+    ],
+    static_libs: [
+        "libcuttlefish_confui",
+        "libcuttlefish_fs",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-DTEEUI_USE_STD_VECTOR",
+    ],
+}
+
diff --git a/guest/hals/confirmationui/README b/guest/hals/confirmationui/README
new file mode 100644
index 0000000..45d4e76
--- /dev/null
+++ b/guest/hals/confirmationui/README
@@ -0,0 +1,20 @@
+## Secure UI Architecture
+
+To implement confirmationui a secure UI architecture is required. This entails a way
+to display the confirmation dialog driven by a reduced trusted computing base, typically
+a trusted execution environment (TEE), without having to rely on Linux and the Android
+system for integrity and authenticity of input events. This implementation provides
+neither. But it provides most of the functionlity required to run a full Android Protected
+Confirmation feature when integrated into a secure UI architecture.
+
+## Secure input (NotSoSecureInput)
+
+This implementation does not provide any security guaranties.
+The input method (NotSoSecureInput) runs a cryptographic protocols that is
+sufficiently secure IFF the end point is implemented on a trustworthy
+secure input device. But since the endpoint is currently in the HAL
+service itself this implementation is not secure.
+
+NOTE that a secure input device end point needs a good source of entropy
+for generating nonces. The current implementation (NotSoSecureInput.cpp#generateNonce)
+uses a constant nonce.
\ No newline at end of file
diff --git a/guest/hals/confirmationui/TrustyConfirmationUI.cpp b/guest/hals/confirmationui/TrustyConfirmationUI.cpp
new file mode 100644
index 0000000..7854332
--- /dev/null
+++ b/guest/hals/confirmationui/TrustyConfirmationUI.cpp
@@ -0,0 +1,248 @@
+/*
+ *
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TrustyConfirmationUI.h"
+
+#include <cutils/properties.h>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+
+using ::teeui::MsgString;
+using ::teeui::MsgVector;
+using ::android::hardware::keymaster::V4_0::HardwareAuthToken;
+using TeeuiRc = ::teeui::ResponseCode;
+
+namespace {
+teeui::UIOption convertUIOption(UIOption uio) {
+    static_assert(uint32_t(UIOption::AccessibilityInverted) ==
+                          uint32_t(teeui::UIOption::AccessibilityInverted) &&
+                      uint32_t(UIOption::AccessibilityMagnified) ==
+                          uint32_t(teeui::UIOption::AccessibilityMagnified),
+                  "teeui::UIOPtion and ::android::hardware::confirmationui::V1_0::UIOption "
+                  "are out of sync");
+    return teeui::UIOption(uio);
+}
+
+inline MsgString hidl2MsgString(const hidl_string& s) {
+    return {s.c_str(), s.c_str() + s.size()};
+}
+template <typename T> inline MsgVector<T> hidl2MsgVector(const hidl_vec<T>& v) {
+    return {v};
+}
+
+inline MsgVector<teeui::UIOption> hidl2MsgVector(const hidl_vec<UIOption>& v) {
+    MsgVector<teeui::UIOption> result(v.size());
+    for (unsigned int i = 0; i < v.size(); ++i) {
+        result[i] = convertUIOption(v[i]);
+    }
+    return result;
+}
+}  // namespace
+
+cuttlefish::SharedFD TrustyConfirmationUI::ConnectToHost() {
+    using namespace std::chrono_literals;
+    while (true) {
+        auto host_fd = cuttlefish::SharedFD::VsockClient(2, host_vsock_port_, SOCK_STREAM);
+        if (host_fd->IsOpen()) {
+            ConfUiLog(INFO) << "Client connection is established";
+            return host_fd;
+        }
+        ConfUiLog(INFO) << "host service is not on. Sleep for 500 ms";
+        std::this_thread::sleep_for(500ms);
+    }
+}
+
+TrustyConfirmationUI::TrustyConfirmationUI()
+    : listener_state_(ListenerState::None),
+      prompt_result_(ResponseCode::Ignored), host_vsock_port_{static_cast<int>(property_get_int64(
+                                                 "ro.boot.vsock_confirmationui_port", 7700))},
+      current_session_id_{10} {
+    ConfUiLog(INFO) << "Connecting to Confirmation UI host listening on port " << host_vsock_port_;
+    host_fd_ = ConnectToHost();
+    auto fetching_cmd = [this]() { HostMessageFetcherLoop(); };
+    if (host_fd_->IsOpen()) {
+        host_cmd_fetcher_thread_ = std::thread(fetching_cmd);
+    }
+}
+
+TrustyConfirmationUI::~TrustyConfirmationUI() {
+    if (host_fd_->IsOpen()) {
+        host_fd_->Close();
+    }
+    if (host_cmd_fetcher_thread_.joinable()) {
+        host_cmd_fetcher_thread_.join();
+    }
+
+    if (listener_state_ != ListenerState::None) {
+        callback_thread_.join();
+    }
+}
+
+void TrustyConfirmationUI::HostMessageFetcherLoop() {
+    while (true) {
+        if (!host_fd_->IsOpen()) {
+            // this happens when TrustyConfirmationUI is destroyed
+            ConfUiLog(ERROR) << "host_fd_ is not open";
+            return;
+        }
+        auto msg = cuttlefish::confui::RecvConfUiMsg(host_fd_);
+        if (!msg) {
+            // socket is broken for now
+            return;
+        }
+        {
+            std::unique_lock<std::mutex> lk(current_session_lock_);
+            if (!current_session_ || msg->GetSessionId() != current_session_->GetSessionId()) {
+                if (!current_session_) {
+                    ConfUiLog(ERROR) << "msg is received but session is null";
+                    continue;
+                }
+                ConfUiLog(ERROR) << "session id mismatch, so ignored"
+                                 << "Received for " << msg->GetSessionId()
+                                 << " but currently running " << current_session_->GetSessionId();
+                continue;
+            }
+            current_session_->Push(std::move(msg));
+        }
+        listener_state_condv_.notify_all();
+    }
+}
+
+void TrustyConfirmationUI::RunSession(sp<IConfirmationResultCallback> resultCB,
+                                      hidl_string promptText, hidl_vec<uint8_t> extraData,
+                                      hidl_string locale, hidl_vec<UIOption> uiOptions) {
+    cuttlefish::SharedFD fd = host_fd_;
+    // ownership of the fd is passed to GuestSession
+    {
+        std::unique_lock<std::mutex> lk(current_session_lock_);
+        current_session_ = std::make_unique<GuestSession>(
+            current_session_id_, listener_state_, listener_state_lock_, listener_state_condv_, fd,
+            hidl2MsgString(promptText), hidl2MsgVector(extraData), hidl2MsgString(locale),
+            hidl2MsgVector(uiOptions));
+    }
+
+    auto [rc, msg, token] = current_session_->PromptUserConfirmation();
+
+    std::unique_lock<std::mutex> lock(listener_state_lock_);  // for listener_state_
+    bool do_callback = (listener_state_ == ListenerState::Interactive ||
+                        listener_state_ == ListenerState::SetupDone) &&
+                       resultCB;
+    prompt_result_ = rc;
+    listener_state_ = ListenerState::Terminating;
+    lock.unlock();
+    if (do_callback) {
+        auto error = resultCB->result(prompt_result_, msg, token);
+        if (!error.isOk()) {
+            ConfUiLog(ERROR) << "Result callback failed " << error.description();
+        }
+        ConfUiLog(INFO) << "Result callback returned.";
+    } else {
+        listener_state_condv_.notify_all();
+    }
+}
+
+// Methods from ::android::hardware::confirmationui::V1_0::IConfirmationUI
+// follow.
+Return<ResponseCode> TrustyConfirmationUI::promptUserConfirmation(
+    const sp<IConfirmationResultCallback>& resultCB, const hidl_string& promptText,
+    const hidl_vec<uint8_t>& extraData, const hidl_string& locale,
+    const hidl_vec<UIOption>& uiOptions) {
+    std::unique_lock<std::mutex> stateLock(listener_state_lock_, std::defer_lock);
+    ConfUiLog(INFO) << "promptUserConfirmation is called";
+
+    if (!stateLock.try_lock()) {
+        return ResponseCode::OperationPending;
+    }
+    switch (listener_state_) {
+    case ListenerState::None:
+        break;
+    case ListenerState::Starting:
+    case ListenerState::SetupDone:
+    case ListenerState::Interactive:
+        return ResponseCode::OperationPending;
+    case ListenerState::Terminating:
+        callback_thread_.join();
+        listener_state_ = ListenerState::None;
+        break;
+    default:
+        return ResponseCode::Unexpected;
+    }
+    assert(listener_state_ == ListenerState::None);
+    listener_state_ = ListenerState::Starting;
+    ConfUiLog(INFO) << "Per promptUserConfirmation, "
+                    << "an active TEE UI session starts";
+    current_session_id_++;
+    auto worker = [this](const sp<IConfirmationResultCallback>& resultCB,
+                         const hidl_string& promptText, const hidl_vec<uint8_t>& extraData,
+                         const hidl_string& locale, const hidl_vec<UIOption>& uiOptions) {
+        RunSession(resultCB, promptText, extraData, locale, uiOptions);
+    };
+    callback_thread_ = std::thread(worker, resultCB, promptText, extraData, locale, uiOptions);
+
+    listener_state_condv_.wait(stateLock, [this] {
+        return listener_state_ == ListenerState::SetupDone ||
+               listener_state_ == ListenerState::Interactive ||
+               listener_state_ == ListenerState::Terminating;
+    });
+    if (listener_state_ == ListenerState::Terminating) {
+        callback_thread_.join();
+        listener_state_ = ListenerState::None;
+        if (prompt_result_ == ResponseCode::Canceled) {
+            // VTS expects this
+            return ResponseCode::OK;
+        }
+        return prompt_result_;
+    }
+    return ResponseCode::OK;
+}
+
+Return<ResponseCode>
+TrustyConfirmationUI::deliverSecureInputEvent(const HardwareAuthToken& auth_token) {
+    ConfUiLog(INFO) << "deliverSecureInputEvent is called";
+    ResponseCode rc = ResponseCode::Ignored;
+    {
+        std::unique_lock<std::mutex> lock(current_session_lock_);
+        if (!current_session_) {
+            return rc;
+        }
+        return current_session_->DeliverSecureInputEvent(auth_token);
+    }
+}
+
+Return<void> TrustyConfirmationUI::abort() {
+    {
+        std::unique_lock<std::mutex> lock(current_session_lock_);
+        if (!current_session_) {
+            return Void();
+        }
+        return current_session_->Abort();
+    }
+}
+
+android::sp<IConfirmationUI> createTrustyConfirmationUI() {
+    return new TrustyConfirmationUI();
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace confirmationui
+}  // namespace hardware
+}  // namespace android
diff --git a/guest/hals/confirmationui/TrustyConfirmationUI.h b/guest/hals/confirmationui/TrustyConfirmationUI.h
new file mode 100644
index 0000000..1742d88
--- /dev/null
+++ b/guest/hals/confirmationui/TrustyConfirmationUI.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H
+#define ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H
+
+#include <atomic>
+#include <condition_variable>
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <thread>
+
+#include <android/hardware/confirmationui/1.0/IConfirmationUI.h>
+#include <android/hardware/keymaster/4.0/types.h>
+#include <hidl/Status.h>
+#include <teeui/generic_messages.h>
+
+#include "common/libs/concurrency/thread_safe_queue.h"
+#include "common/libs/confui/confui.h"
+#include "common/libs/fs/shared_fd.h"
+#include "guest_session.h"
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::sp;
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+
+class TrustyConfirmationUI : public IConfirmationUI {
+  public:
+    using ConfUiMessage = cuttlefish::confui::ConfUiMessage;
+    using ConfUiAckMessage = cuttlefish::confui::ConfUiAckMessage;
+    using ListenerState = GuestSession::ListenerState;
+
+    TrustyConfirmationUI();
+    virtual ~TrustyConfirmationUI();
+    // Methods from ::android::hardware::confirmationui::V1_0::IConfirmationUI
+    // follow.
+    Return<ResponseCode> promptUserConfirmation(const sp<IConfirmationResultCallback>& resultCB,
+                                                const hidl_string& promptText,
+                                                const hidl_vec<uint8_t>& extraData,
+                                                const hidl_string& locale,
+                                                const hidl_vec<UIOption>& uiOptions) override;
+    Return<ResponseCode> deliverSecureInputEvent(
+        const ::android::hardware::keymaster::V4_0::HardwareAuthToken& secureInputToken) override;
+
+    Return<void> abort() override;
+
+  private:
+    /*
+     * Note for implementation
+     *
+     * The TEE UI session cannot be pre-emptied normally. The session will have an
+     * exclusive control for the input and the screen. Only when something goes
+     * wrong, it can be aborted by abort().
+     *
+     * Another thing is that promptUserConfirmation() may return without waiting
+     * for the resultCB is completed. When it returns early, it still returns
+     * ResponseCode::OK. In that case, the promptUserConfirmation() could actually
+     * fail -- e.g. the input device is broken down afterwards, the user never
+     * gave an input until timeout, etc. Then, the resultCB would be called with
+     * an appropriate error code. However, even in that case, most of the time
+     * promptUserConfirmation() returns OK. Only when the initial set up for
+     * confirmation UI fails, promptUserConfirmation() may return non-OK.
+     *
+     * So, the implementation is roughly:
+     *   1. If there's another session going on, return with ResponseCode::Ignored
+     *      and the return is immediate
+     *   2. If there's a zombie, collect the zombie and go to 3
+     *   3. If there's nothing, start a new session in a new thread, and return
+     *      the promptUserConfirmation() call as early as possible
+     *
+     * Another issue is to maintain/define the ownership of vsock. For now,
+     * a message fetcher (from the host) will see if the vsock is ok, and
+     * reconnect if not. But, eventually, the new session should establish a
+     * new connection/client vsock, and the new session should own the fetcher
+     * thread.
+     */
+    std::thread callback_thread_;
+    ListenerState listener_state_;
+
+    std::mutex listener_state_lock_;
+    std::condition_variable listener_state_condv_;
+    ResponseCode prompt_result_;
+
+    // client socket to the host
+    int host_vsock_port_;
+    cuttlefish::SharedFD host_fd_;
+
+    // ack, response, command from the host, and the abort command from the guest
+    std::atomic<std::uint32_t> current_session_id_;
+    std::mutex current_session_lock_;
+    std::unique_ptr<GuestSession> current_session_;
+    std::thread host_cmd_fetcher_thread_;
+
+    cuttlefish::SharedFD ConnectToHost();
+    void HostMessageFetcherLoop();
+    void RunSession(sp<IConfirmationResultCallback> resultCB, hidl_string promptText,
+                    hidl_vec<uint8_t> extraData, hidl_string locale, hidl_vec<UIOption> uiOptions);
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace confirmationui
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H
diff --git a/guest/hals/confirmationui/android.hardware.confirmationui@1.0-service.cuttlefish.rc b/guest/hals/confirmationui/android.hardware.confirmationui@1.0-service.cuttlefish.rc
new file mode 100644
index 0000000..81dfd49
--- /dev/null
+++ b/guest/hals/confirmationui/android.hardware.confirmationui@1.0-service.cuttlefish.rc
@@ -0,0 +1,5 @@
+service confirmationui-1-0 /vendor/bin/hw/android.hardware.confirmationui@1.0-service.cuttlefish
+    interface android.hardware.confirmationui@1.0::IConfirmationUI default
+    class hal
+    user system
+    group drmrpc input system
diff --git a/guest/hals/ril/reference-libril/android.hardware.radio.config@1.3.xml b/guest/hals/confirmationui/android.hardware.confirmationui@1.0-service.cuttlefish.xml
similarity index 62%
rename from guest/hals/ril/reference-libril/android.hardware.radio.config@1.3.xml
rename to guest/hals/confirmationui/android.hardware.confirmationui@1.0-service.cuttlefish.xml
index 97bf66d..9008b87 100644
--- a/guest/hals/ril/reference-libril/android.hardware.radio.config@1.3.xml
+++ b/guest/hals/confirmationui/android.hardware.confirmationui@1.0-service.cuttlefish.xml
@@ -1,10 +1,10 @@
 <manifest version="1.0" type="device">
     <hal format="hidl">
-        <name>android.hardware.radio.config</name>
+        <name>android.hardware.confirmationui</name>
         <transport>hwbinder</transport>
-        <version>1.3</version>
+        <version>1.0</version>
         <interface>
-            <name>IRadioConfig</name>
+        <name>IConfirmationUI</name>
             <instance>default</instance>
         </interface>
     </hal>
diff --git a/guest/hals/confirmationui/guest_session.cpp b/guest/hals/confirmationui/guest_session.cpp
new file mode 100644
index 0000000..aa2ab12
--- /dev/null
+++ b/guest/hals/confirmationui/guest_session.cpp
@@ -0,0 +1,265 @@
+/*
+ *
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "guest_session.h"
+
+#include <future>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+using TeeuiRc = teeui::ResponseCode;
+
+GuestSession::ResultTriple GuestSession::PromptUserConfirmation() {
+    std::unique_lock<std::mutex> stateLock(listener_state_lock_);
+    /*
+     * This is the main listener thread function. The listener thread life cycle
+     * is equivalent to the life cycle of a single confirmation request. The life
+     * cycle is divided in four phases.
+     *  * The starting phase:
+     *    * Drives the cuttlefish confirmation UI session on the host side, too
+     *
+     * Note: During the starting phase the hwbinder service thread is blocked and
+     * waiting for possible Errors. If the setup phase concludes successfully, the
+     * hwbinder service thread gets unblocked and returns successfully. Errors
+     * that occur after the first phase are delivered by callback interface.
+     *
+     * For cuttlefish, it means that the guest will conduct a blocking wait for
+     * an ack to kStart.
+     *
+     *  * The 2nd phase - non interactive phase
+     *    * After a grace period:
+     *      * guest will pick up cuttlefish host's ack to kStart
+     *
+     *  * The 3rd phase - interactive phase
+     *    * We wait to any external event
+     *      * Abort
+     *      * Secure user input asserted
+     *    * The result is fetched from the TA.
+     *
+     *  * The 4th phase - cleanup
+     *    * Sending the kStop command to the cuttlefish host, and wait for ack
+     */
+
+    GuestSession::ResultTriple error;
+    auto& error_rc = std::get<ResponseCode>(error);
+    error_rc = ResponseCode::SystemError;
+
+    CHECK(listener_state_ == ListenerState::Starting) << "ListenerState should be Starting";
+
+    // initiate prompt
+    ConfUiLog(INFO) << "Initiating prompt";
+    const std::uint32_t payload_lower_bound =
+        static_cast<std::uint32_t>(prompt_text_.size() + extra_data_.size());
+    const std::uint32_t upper_bound =
+        static_cast<std::uint32_t>(cuttlefish::confui::kMaxMessageLength);
+    if (payload_lower_bound > upper_bound) {
+        ConfUiLog(INFO) << "UI message too long to send to the host";
+        // message is too long anyway, and don't send it to the host
+        error_rc = ResponseCode::UIErrorMessageTooLong;
+        return error;
+    }
+    SerializedSend(cuttlefish::confui::SendStartCmd, host_fd_, session_name_, prompt_text_,
+                   extra_data_, locale_, ui_options_);
+    ConfUiLog(INFO) << "Session " << GetSessionId() << " started on both the guest and the host";
+
+    auto clean_up_and_get_first = [&]() -> std::unique_ptr<ConfUiMessage> {
+        // blocking wait to get the first msg that belongs to this session
+        while (true) {
+            auto first_curr_session_msg = incoming_msg_queue_.Pop();
+            if (!first_curr_session_msg ||
+                first_curr_session_msg->GetSessionId() != GetSessionId()) {
+                continue;
+            }
+            return std::move(first_curr_session_msg);
+        }
+    };
+
+    /*
+     * Unconditionally wait ack, or host abort
+     *
+     * First couple of messages could be from the previous session.
+     * We should clear them up.
+     *
+     * Even though the guest HAL sends kAbort to the host, the kAbort
+     * does not happen immediately. Between the incoming_msg_queue_.FlushAll()
+     * and the actual abort on the host, there could still be messages
+     * sent from the host to the guest. As these lines are the first read
+     * for the current session, we clear up the preceding messages
+     * from the previous session until we see the message for the current
+     * session.
+     *
+     * Note that abort() call puts the Abort command in the queue. So,
+     * it will also show up in incoming_msg_queue_
+     *
+     */
+    auto first_msg = std::move(clean_up_and_get_first());
+
+    cuttlefish::confui::ConfUiAckMessage& start_ack_msg =
+        static_cast<cuttlefish::confui::ConfUiAckMessage&>(*first_msg);
+    if (!start_ack_msg.IsSuccess()) {
+        // handle errors: MALFORMED_UTF8 or Message too long
+        const std::string error_msg = start_ack_msg.GetStatusMessage();
+        if (error_msg == cuttlefish::confui::HostError::kMessageTooLongError) {
+            ConfUiLog(ERROR) << "Message + Extra data + Meta info were too long";
+            error_rc = ResponseCode::UIErrorMessageTooLong;
+        }
+        if (error_msg == cuttlefish::confui::HostError::kIncorrectUTF8) {
+            ConfUiLog(ERROR) << "Message is incorrectly UTF-encoded";
+            error_rc = ResponseCode::UIErrorMalformedUTF8Encoding;
+        }
+        return error;
+    }
+
+    //  ############################## Start 2nd Phase #############################################
+    listener_state_ = ListenerState::SetupDone;
+    ConfUiLog(INFO) << "Transition to SetupDone";
+    stateLock.unlock();
+    listener_state_condv_.notify_all();
+
+    // cuttlefish does not need the second phase to implement HAL APIs
+    // input was already prepared before the confirmation UI screen was rendered
+
+    //  ############################## Start 3rd Phase - interactive phase #########################
+    stateLock.lock();
+    listener_state_ = ListenerState::Interactive;
+    ConfUiLog(INFO) << "Transition to Interactive";
+    stateLock.unlock();
+    listener_state_condv_.notify_all();
+
+    // give deliverSecureInputEvent a chance to interrupt
+
+    // wait for an input but should not block deliverSecureInputEvent or Abort
+    // Thus, it should not hold the stateLock
+    std::mutex input_ready_mtx;
+    std::condition_variable input_ready_cv_;
+    std::unique_lock<std::mutex> input_ready_lock(input_ready_mtx);
+    bool input_ready = false;
+    auto wait_input_and_signal = [&]() -> std::unique_ptr<ConfUiMessage> {
+        auto msg = incoming_msg_queue_.Pop();
+        {
+            std::unique_lock<std::mutex> lock(input_ready_mtx);
+            input_ready = true;
+            input_ready_cv_.notify_one();
+        }
+        return msg;
+    };
+    auto input_and_signal_future = std::async(std::launch::async, wait_input_and_signal);
+    input_ready_cv_.wait(input_ready_lock, [&]() { return input_ready; });
+    // now an input is ready, so let's acquire the stateLock
+
+    stateLock.lock();
+    auto user_or_abort = input_and_signal_future.get();
+
+    if (user_or_abort->GetType() == cuttlefish::confui::ConfUiCmd::kAbort) {
+        ConfUiLog(ERROR) << "Abort called or the user/host aborted"
+                         << " while waiting user response";
+        return {ResponseCode::Aborted, {}, {}};
+    }
+    if (user_or_abort->GetType() == cuttlefish::confui::ConfUiCmd::kCliAck) {
+        auto& ack_msg = static_cast<cuttlefish::confui::ConfUiAckMessage&>(*user_or_abort);
+        if (ack_msg.IsSuccess()) {
+            ConfUiLog(ERROR) << "When host failed, it is supposed to send "
+                             << "kCliAck with fail, but this is kCliAck with success";
+        }
+        error_rc = ResponseCode::SystemError;
+        return error;
+    }
+    cuttlefish::confui::ConfUiCliResponseMessage& user_response =
+        static_cast<cuttlefish::confui::ConfUiCliResponseMessage&>(*user_or_abort);
+
+    // pick, see if it is response, abort cmd
+    // handle abort or error response here
+    ConfUiLog(INFO) << "Making up the result";
+
+    // make up the result triple
+    if (user_response.GetResponse() == cuttlefish::confui::UserResponse::kCancel) {
+        SerializedSend(cuttlefish::confui::SendStopCmd, host_fd_, GetSessionId());
+        return {ResponseCode::Canceled, {}, {}};
+    }
+
+    if (user_response.GetResponse() != cuttlefish::confui::UserResponse::kConfirm) {
+        ConfUiLog(ERROR) << "Unexpected user response that is " << user_response.GetResponse();
+        return error;
+    }
+    SerializedSend(cuttlefish::confui::SendStopCmd, host_fd_, GetSessionId());
+    //  ############################## Start 4th Phase - cleanup ##################################
+    return {ResponseCode::OK, user_response.GetMessage(), user_response.GetSign()};
+}
+
+Return<ResponseCode> GuestSession::DeliverSecureInputEvent(
+    const android::hardware::keymaster::V4_0::HardwareAuthToken& auth_token) {
+    ResponseCode rc = ResponseCode::Ignored;
+    {
+        /*
+         * deliverSecureInputEvent is only used by the VTS test to mock human input. A correct
+         * implementation responds with a mock confirmation token signed with a test key. The
+         * problem is that the non interactive grace period was not formalized in the HAL spec,
+         * so that the VTS test does not account for the grace period. (It probably should.)
+         * This means we can only pass the VTS test if we block until the grace period is over
+         * (SetupDone -> Interactive) before we deliver the input event.
+         *
+         * The true secure input is delivered by a different mechanism and gets ignored -
+         * not queued - until the grace period is over.
+         *
+         */
+        std::unique_lock<std::mutex> stateLock(listener_state_lock_);
+        listener_state_condv_.wait(stateLock,
+                                   [this] { return listener_state_ != ListenerState::SetupDone; });
+        if (listener_state_ != ListenerState::Interactive) return ResponseCode::Ignored;
+        if (static_cast<TestModeCommands>(auth_token.challenge) == TestModeCommands::OK_EVENT) {
+            SerializedSend(cuttlefish::confui::SendUserSelection, host_fd_, GetSessionId(),
+                           cuttlefish::confui::UserResponse::kConfirm);
+        } else {
+            SerializedSend(cuttlefish::confui::SendUserSelection, host_fd_, GetSessionId(),
+                           cuttlefish::confui::UserResponse::kCancel);
+        }
+        rc = ResponseCode::OK;
+    }
+    listener_state_condv_.notify_all();
+    // VTS test expect an OK response if the event was successfully delivered.
+    // But since the TA returns the callback response now, we have to translate
+    // Canceled into OK. Canceled is only returned if the delivered event canceled
+    // the operation, which means that the event was successfully delivered. Thus
+    // we return OK.
+    if (rc == ResponseCode::Canceled) return ResponseCode::OK;
+    return rc;
+}
+
+Return<void> GuestSession::Abort() {
+    {
+        std::unique_lock<std::mutex> stateLock(listener_state_lock_);
+        if (listener_state_ == ListenerState::SetupDone ||
+            listener_state_ == ListenerState::Interactive) {
+            if (host_fd_->IsOpen()) {
+                SerializedSend(cuttlefish::confui::SendAbortCmd, host_fd_, GetSessionId());
+            }
+            using cuttlefish::confui::ConfUiAbortMessage;
+            auto local_abort_cmd = std::make_unique<ConfUiAbortMessage>(GetSessionId());
+            incoming_msg_queue_.Push(std::move(local_abort_cmd));
+        }
+    }
+    listener_state_condv_.notify_all();
+    return Void();
+}
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace confirmationui
+}  // namespace hardware
+}  // namespace android
diff --git a/guest/hals/confirmationui/guest_session.h b/guest/hals/confirmationui/guest_session.h
new file mode 100644
index 0000000..0dceffe
--- /dev/null
+++ b/guest/hals/confirmationui/guest_session.h
@@ -0,0 +1,146 @@
+/*
+ *
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/logging.h>
+#include <android/hardware/confirmationui/1.0/types.h>
+#include <android/hardware/keymaster/4.0/types.h>
+
+#include <condition_variable>
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "common/libs/concurrency/thread_safe_queue.h"
+#include "common/libs/confui/confui.h"
+#include "common/libs/fs/shared_fd.h"
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+class GuestSession {
+  public:
+    using ConfUiMessage = cuttlefish::confui::ConfUiMessage;
+    using ConfUiAckMessage = cuttlefish::confui::ConfUiAckMessage;
+    using Queue = cuttlefish::ThreadSafeQueue<std::unique_ptr<ConfUiMessage>>;
+    using QueueImpl = Queue::QueueImpl;
+
+    enum class ListenerState : uint32_t {
+        None = 0,
+        Starting = 1,
+        SetupDone = 2,
+        Interactive = 3,
+        Terminating = 4,
+    };
+
+    GuestSession(const std::uint32_t session_id, ListenerState& listener_state,
+                 std::mutex& listener_state_lock, std::condition_variable& listener_state_condv,
+                 cuttlefish::SharedFD host_fd, const teeui::MsgString& promptText,
+                 const teeui::MsgVector<uint8_t>& extraData, const teeui::MsgString& locale,
+                 const teeui::MsgVector<teeui::UIOption>& uiOptions)
+        : prompt_text_{promptText.begin(), promptText.end()}, extra_data_{extraData.begin(),
+                                                                          extraData.end()},
+          locale_{locale.begin(), locale.end()}, ui_options_{uiOptions.begin(), uiOptions.end()},
+          listener_state_(listener_state), listener_state_lock_(listener_state_lock),
+          listener_state_condv_(listener_state_condv), host_fd_{host_fd},
+          session_name_(MakeName(session_id)),
+          incoming_msg_queue_(
+              20, [this](GuestSession::QueueImpl* impl) { return QueueFullHandler(impl); }) {}
+
+    ~GuestSession() {
+        // the thread for PromptUserConfirmation is still alive
+        // the host_fd_ may be alive
+        auto state = listener_state_;
+        if (state == ListenerState::SetupDone || state == ListenerState::Interactive) {
+            Abort();
+        }
+        // TODO(kwstephenkim): close fd once Session takes the ownership of fd
+        // join host_cmd_fetcher_thread_ once Session takes the ownership of fd
+    }
+
+    using ResultTriple =
+        std::tuple<ResponseCode, teeui::MsgVector<uint8_t>, teeui::MsgVector<uint8_t>>;
+    ResultTriple PromptUserConfirmation();
+
+    Return<ResponseCode> DeliverSecureInputEvent(
+        const ::android::hardware::keymaster::V4_0::HardwareAuthToken& secureInputToken);
+
+    Return<void> Abort();
+    std::string GetSessionId() const { return session_name_; }
+
+    void Push(std::unique_ptr<ConfUiMessage>&& msg) { incoming_msg_queue_.Push(std::move(msg)); }
+
+  private:
+    template <typename F, typename... Args>
+    bool SerializedSend(F&& f, cuttlefish::SharedFD fd, Args&&... args) {
+        if (!fd->IsOpen()) {
+            return false;
+        }
+        std::unique_lock<std::mutex> lock(send_serializer_mtx_);
+        return f(fd, std::forward<Args>(args)...);
+    }
+
+    void QueueFullHandler(QueueImpl* queue_impl) {
+        if (!queue_impl) {
+            LOG(ERROR) << "Registered queue handler is "
+                       << "seeing nullptr for queue implementation.";
+            return;
+        }
+        const auto n = (queue_impl->size()) / 2;
+        // pop front half
+        queue_impl->erase(queue_impl->begin(), queue_impl->begin() + n);
+    }
+
+    std::string MakeName(const std::uint32_t i) const {
+        return "ConfirmationUiSession" + std::to_string(i);
+    }
+    std::string prompt_text_;
+    std::vector<std::uint8_t> extra_data_;
+    std::string locale_;
+    std::vector<teeui::UIOption> ui_options_;
+
+    /*
+     * lister_state_lock_ coordinates multiple threads that may
+     * call the three Confirmation UI HAL APIs concurrently
+     */
+    ListenerState& listener_state_;
+    std::mutex& listener_state_lock_;
+    std::condition_variable& listener_state_condv_;
+    cuttlefish::SharedFD host_fd_;
+
+    const std::string session_name_;
+    Queue incoming_msg_queue_;
+
+    /*
+     * multiple threads could try to write on the vsock at the
+     * same time. E.g. promptUserConfirmation() thread sends
+     * a command while abort() is being called. The abort() thread
+     * will try to write an abort command concurrently.
+     */
+    std::mutex send_serializer_mtx_;
+};
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace confirmationui
+}  // namespace hardware
+}  // namespace android
diff --git a/guest/hals/confirmationui/include/TrustyConfirmationuiHal.h b/guest/hals/confirmationui/include/TrustyConfirmationuiHal.h
new file mode 100644
index 0000000..2ab9389
--- /dev/null
+++ b/guest/hals/confirmationui/include/TrustyConfirmationuiHal.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/hardware/confirmationui/1.0/IConfirmationUI.h>
+
+namespace android {
+namespace hardware {
+namespace confirmationui {
+namespace V1_0 {
+namespace implementation {
+
+android::sp<IConfirmationUI> createTrustyConfirmationUI();
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace confirmationui
+}  // namespace hardware
+}  // namespace android
diff --git a/guest/hals/confirmationui/include/TrustyIpc.h b/guest/hals/confirmationui/include/TrustyIpc.h
new file mode 100644
index 0000000..eb764bc
--- /dev/null
+++ b/guest/hals/confirmationui/include/TrustyIpc.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/*
+ * This interface is shared between Android and Trusty. There is a copy in each
+ * repository. They must be kept in sync.
+ */
+
+#define CONFIRMATIONUI_PORT "com.android.trusty.confirmationui"
+
+/**
+ * enum confirmationui_cmd - command identifiers for ConfirmationUI interface
+ * @CONFIRMATIONUI_RESP_BIT:  response bit set as part of response
+ * @CONFIRMATIONUI_REQ_SHIFT: number of bits used by response bit
+ * @CONFIRMATIONUI_CMD_INIT:  command to initialize session
+ * @CONFIRMATIONUI_CMD_MSG:   command to send ConfirmationUI messages
+ */
+enum confirmationui_cmd : uint32_t {
+    CONFIRMATIONUI_RESP_BIT = 1,
+    CONFIRMATIONUI_REQ_SHIFT = 1,
+
+    CONFIRMATIONUI_CMD_INIT = (1 << CONFIRMATIONUI_REQ_SHIFT),
+    CONFIRMATIONUI_CMD_MSG = (2 << CONFIRMATIONUI_REQ_SHIFT),
+};
+
+/**
+ * struct confirmationui_hdr - header for ConfirmationUI messages
+ * @cmd: command identifier
+ *
+ * Note that no messages return a status code. Any error on the server side
+ * results in the connection being closed. So, operations can be assumed to be
+ * successful if they return a response.
+ */
+struct confirmationui_hdr {
+    uint32_t cmd;
+};
+
+/**
+ * struct confirmationui_init_req - arguments for request to initialize a
+ *                                  session
+ * @shm_len: length of memory region being shared
+ *
+ * A handle to a memory region must be sent along with this message. This memory
+ * is send to ConfirmationUI messages.
+ */
+struct confirmationui_init_req {
+    uint32_t shm_len;
+};
+
+/**
+ * struct confirmationui_msg_args - arguments for sending a message
+ * @msg_len: length of message being sent
+ *
+ * Contents of the message are located in the shared memory region that is
+ * established using %CONFIRMATIONUI_CMD_INIT.
+ *
+ * ConfirmationUI messages can travel both ways.
+ */
+struct confirmationui_msg_args {
+    uint32_t msg_len;
+};
+
+#define CONFIRMATIONUI_MAX_MSG_SIZE 0x2000
diff --git a/guest/hals/confirmationui/service.cpp b/guest/hals/confirmationui/service.cpp
new file mode 100644
index 0000000..dd7e84b
--- /dev/null
+++ b/guest/hals/confirmationui/service.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <hidl/HidlTransportSupport.h>
+
+#include <TrustyConfirmationuiHal.h>
+
+using android::sp;
+using android::hardware::confirmationui::V1_0::implementation::createTrustyConfirmationUI;
+
+int main() {
+    ::android::hardware::configureRpcThreadpool(1, true /*willJoinThreadpool*/);
+    auto service = createTrustyConfirmationUI();
+    auto status = service->registerAsService();
+    if (status != android::OK) {
+        LOG(FATAL) << "Could not register service for ConfirmationUI 1.0 (" << status << ")";
+        return -1;
+    }
+    ::android::hardware::joinRpcThreadpool();
+    return -1;
+}
diff --git a/guest/hals/health/Android.bp b/guest/hals/health/Android.bp
index 81d19a3..cb9d866 100644
--- a/guest/hals/health/Android.bp
+++ b/guest/hals/health/Android.bp
@@ -17,6 +17,58 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+cc_defaults {
+    name: "android.hardware.health-service.cuttlefish-defaults",
+    relative_install_path: "hw",
+    vintf_fragments: ["android.hardware.health-service.cuttlefish.xml"],
+
+    srcs: [
+        "health-aidl.cpp",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    static_libs: [
+        "android.hardware.health-translate-ndk",
+        "libbatterymonitor",
+        "libhealthloop",
+        "libhealth_aidl_impl",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "libcutils",
+        "libhidlbase",
+        "liblog",
+        "libutils",
+        "android.hardware.health-V1-ndk",
+    ],
+
+    defaults: ["enabled_on_p_and_later"],
+}
+
+cc_binary {
+    name: "android.hardware.health-service.cuttlefish",
+    defaults: ["android.hardware.health-service.cuttlefish-defaults"],
+    proprietary: true,
+    init_rc: ["android.hardware.health-service.cuttlefish.rc"],
+    overrides: ["charger"],
+}
+
+cc_binary {
+    name: "android.hardware.health-service.cuttlefish_recovery",
+    defaults: ["android.hardware.health-service.cuttlefish-defaults"],
+    recovery: true,
+    init_rc: ["android.hardware.health-service.cuttlefish_recovery.rc"],
+    overrides: ["charger.recovery"],
+}
+
+// Deprecated. Retained to be used on other devices. It is not installed on cuttlefish.
+// TODO(b/210183170): Delete once other devices transition to the AIDL HAL.
 cc_library_shared {
     name: "android.hardware.health@2.1-impl-cuttlefish",
     stem: "android.hardware.health@2.0-impl-2.1-cuttlefish",
@@ -26,7 +78,7 @@
     relative_install_path: "hw",
 
     srcs: [
-        "health.cpp",
+        "health-hidl.cpp",
     ],
 
     cflags: [
diff --git a/guest/hals/health/android.hardware.health-service.cuttlefish.rc b/guest/hals/health/android.hardware.health-service.cuttlefish.rc
new file mode 100644
index 0000000..8c2f153
--- /dev/null
+++ b/guest/hals/health/android.hardware.health-service.cuttlefish.rc
@@ -0,0 +1,8 @@
+service vendor.health-cuttlefish /vendor/bin/hw/android.hardware.health-service.cuttlefish
+    class hal
+    user system
+    group system
+    capabilities WAKE_ALARM BLOCK_SUSPEND
+    file /dev/kmsg w
+
+# cuttlefish has no charger mode.
diff --git a/guest/hals/health/android.hardware.health-service.cuttlefish.xml b/guest/hals/health/android.hardware.health-service.cuttlefish.xml
new file mode 100644
index 0000000..98026cb
--- /dev/null
+++ b/guest/hals/health/android.hardware.health-service.cuttlefish.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.health</name>
+        <version>1</version>
+        <fqname>IHealth/default</fqname>
+    </hal>
+</manifest>
diff --git a/guest/hals/health/android.hardware.health-service.cuttlefish_recovery.rc b/guest/hals/health/android.hardware.health-service.cuttlefish_recovery.rc
new file mode 100644
index 0000000..58e4405
--- /dev/null
+++ b/guest/hals/health/android.hardware.health-service.cuttlefish_recovery.rc
@@ -0,0 +1,7 @@
+service vendor.health-cuttlefish /system/bin/hw/android.hardware.health-service.cuttlefish_recovery
+    class hal
+    seclabel u:r:hal_health_default:s0
+    user system
+    group system
+    capabilities WAKE_ALARM BLOCK_SUSPEND
+    file /dev/kmsg w
diff --git a/guest/hals/health/health-aidl.cpp b/guest/hals/health/health-aidl.cpp
new file mode 100644
index 0000000..595971f
--- /dev/null
+++ b/guest/hals/health/health-aidl.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "android.hardware.health-service.cuttlefish"
+
+#include <memory>
+#include <string_view>
+
+#include <android-base/logging.h>
+#include <android/binder_interface_utils.h>
+#include <health-impl/Health.h>
+#include <health/utils.h>
+
+using ::aidl::android::hardware::health::BatteryHealth;
+using ::aidl::android::hardware::health::BatteryStatus;
+using ::aidl::android::hardware::health::HalHealthLoop;
+using ::aidl::android::hardware::health::Health;
+using ::aidl::android::hardware::health::HealthInfo;
+using ::aidl::android::hardware::health::IHealth;
+using ::android::hardware::health::InitHealthdConfig;
+using ::ndk::ScopedAStatus;
+using ::ndk::SharedRefBase;
+using namespace std::literals;
+
+namespace aidl::android::hardware::health {
+
+// Health HAL implementation for cuttlefish. Note that in this implementation,
+// cuttlefish pretends to be a device with a battery being charged.
+// Implementations on real devices should not insert these fake values. For
+// example, a battery-less device should report batteryPresent = false and
+// batteryStatus = UNKNOWN.
+
+class HealthImpl : public Health {
+ public:
+  // Inherit constructor.
+  using Health::Health;
+  virtual ~HealthImpl() {}
+
+  ScopedAStatus getChargeCounterUah(int32_t* out) override;
+  ScopedAStatus getCurrentNowMicroamps(int32_t* out) override;
+  ScopedAStatus getCurrentAverageMicroamps(int32_t* out) override;
+  ScopedAStatus getCapacity(int32_t* out) override;
+  ScopedAStatus getChargeStatus(BatteryStatus* out) override;
+
+ protected:
+  void UpdateHealthInfo(HealthInfo* health_info) override;
+};
+
+void HealthImpl::UpdateHealthInfo(HealthInfo* health_info) {
+  health_info->chargerAcOnline = true;
+  health_info->chargerUsbOnline = true;
+  health_info->chargerWirelessOnline = false;
+  health_info->maxChargingCurrentMicroamps = 500000;
+  health_info->maxChargingVoltageMicrovolts = 5000000;
+  health_info->batteryStatus = BatteryStatus::CHARGING;
+  health_info->batteryHealth = BatteryHealth::GOOD;
+  health_info->batteryPresent = true;
+  health_info->batteryLevel = 85;
+  health_info->batteryVoltageMillivolts = 3600;
+  health_info->batteryTemperatureTenthsCelsius = 350;
+  health_info->batteryCurrentMicroamps = 400000;
+  health_info->batteryCycleCount = 32;
+  health_info->batteryFullChargeUah = 4000000;
+  health_info->batteryChargeCounterUah = 1900000;
+  health_info->batteryTechnology = "Li-ion";
+}
+
+ScopedAStatus HealthImpl::getChargeCounterUah(int32_t* out) {
+  *out = 1900000;
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus HealthImpl::getCurrentNowMicroamps(int32_t* out) {
+  *out = 400000;
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus HealthImpl::getCurrentAverageMicroamps(int32_t*) {
+  return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ScopedAStatus HealthImpl::getCapacity(int32_t* out) {
+  *out = 85;
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus HealthImpl::getChargeStatus(BatteryStatus* out) {
+  *out = BatteryStatus::CHARGING;
+  return ScopedAStatus::ok();
+}
+
+}  // namespace aidl::android::hardware::health
+
+int main(int, [[maybe_unused]] char** argv) {
+#ifdef __ANDROID_RECOVERY__
+  android::base::InitLogging(argv, android::base::KernelLogger);
+#endif
+  // Cuttlefish does not support offline-charging mode, hence do not handle
+  // --charger option.
+  using aidl::android::hardware::health::HealthImpl;
+  LOG(INFO) << "Starting health HAL.";
+  auto config = std::make_unique<healthd_config>();
+  InitHealthdConfig(config.get());
+  auto binder = SharedRefBase::make<HealthImpl>("default", std::move(config));
+  auto hal_health_loop = std::make_shared<HalHealthLoop>(binder, binder);
+  return hal_health_loop->StartLoop();
+}
diff --git a/guest/hals/health/health.cpp b/guest/hals/health/health-hidl.cpp
similarity index 100%
rename from guest/hals/health/health.cpp
rename to guest/hals/health/health-hidl.cpp
diff --git a/guest/hals/health/storage/Android.bp b/guest/hals/health/storage/Android.bp
index dc57d0f..1ca807d1 100644
--- a/guest/hals/health/storage/Android.bp
+++ b/guest/hals/health/storage/Android.bp
@@ -38,7 +38,7 @@
     ],
 
     shared_libs: [
-        "android.hardware.health.storage-V1-ndk_platform",
+        "android.hardware.health.storage-V1-ndk",
         "libbase",
         "libbinder_ndk",
         "libutils",
diff --git a/host/commands/tapsetiff/Android.bp b/guest/hals/hostapd/Android.bp
similarity index 67%
copy from host/commands/tapsetiff/Android.bp
copy to guest/hals/hostapd/Android.bp
index 1d7dedb..a72b3b7 100644
--- a/host/commands/tapsetiff/Android.bp
+++ b/guest/hals/hostapd/Android.bp
@@ -1,5 +1,4 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,10 +13,15 @@
 // limitations under the License.
 
 package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
+    default_applicable_licenses: [
+        "external_wpa_supplicant_8_license",
+    ],
 }
 
-sh_binary_host {
-    name: "tapsetiff",
-    src: "tapsetiff.py",
+cc_binary {
+    name: "hostapd_cf",
+    defaults: ["hostapd_defaults"],
+    static_libs: [
+        "lib_driver_cmd_simulated_cf_bp",
+    ],
 }
diff --git a/guest/hals/identity/Android.bp b/guest/hals/identity/Android.bp
new file mode 100644
index 0000000..c0142ee
--- /dev/null
+++ b/guest/hals/identity/Android.bp
@@ -0,0 +1,61 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "android.hardware.identity-service.remote",
+    relative_install_path: "hw",
+    init_rc: ["android.hardware.identity-service.remote.rc"],
+    vintf_fragments: ["android.hardware.identity-service.remote.xml"],
+    vendor: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-g",
+    ],
+    shared_libs: [
+        "liblog",
+        "libcrypto",
+        "libbinder_ndk",
+        "libkeymaster_messages",
+    ],
+    static_libs: [
+        "libbase",
+        "libcppbor_external",
+        "libcppcose_rkp",
+        "libutils",
+        "libsoft_attestation_cert",
+        "libkeymaster_portable",
+        "libsoft_attestation_cert",
+        "libpuresoftkeymasterdevice",
+        "android.hardware.identity-support-lib",
+        "android.hardware.identity-V3-ndk",
+        "android.hardware.keymaster-V3-ndk",
+        "android.hardware.security.keymint-V1-ndk",
+    ],
+    local_include_dirs: [
+        "common",
+        "libeic",
+    ],
+    srcs: [
+        "service.cpp",
+        "RemoteSecureHardwareProxy.cpp",
+        "common/IdentityCredential.cpp",
+        "common/IdentityCredentialStore.cpp",
+        "common/WritableIdentityCredential.cpp",
+        "libeic/EicCbor.c",
+        "libeic/EicPresentation.c",
+        "libeic/EicProvisioning.c",
+        "libeic/EicOpsImpl.cc",
+    ],
+    required: [
+        "android.hardware.identity_credential.remote.xml",
+    ],
+}
+
+prebuilt_etc {
+    name: "android.hardware.identity_credential.remote.xml",
+    sub_dir: "permissions",
+    vendor: true,
+    src: "android.hardware.identity_credential.remote.xml",
+}
diff --git a/guest/hals/identity/OWNERS b/guest/hals/identity/OWNERS
new file mode 100644
index 0000000..190f95c
--- /dev/null
+++ b/guest/hals/identity/OWNERS
@@ -0,0 +1 @@
+include /platform/hardware/interfaces:/identity/OWNERS
diff --git a/guest/hals/identity/RemoteSecureHardwareProxy.cpp b/guest/hals/identity/RemoteSecureHardwareProxy.cpp
new file mode 100644
index 0000000..3ec8aaa
--- /dev/null
+++ b/guest/hals/identity/RemoteSecureHardwareProxy.cpp
@@ -0,0 +1,412 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "RemoteSecureHardwareProxy"
+
+#include "RemoteSecureHardwareProxy.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <string.h>
+
+#include <openssl/sha.h>
+
+#include <openssl/aes.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include <libeic.h>
+
+using ::std::optional;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+namespace android::hardware::identity {
+
+// ----------------------------------------------------------------------
+
+RemoteSecureHardwareProvisioningProxy::RemoteSecureHardwareProvisioningProxy() {
+}
+
+RemoteSecureHardwareProvisioningProxy::
+    ~RemoteSecureHardwareProvisioningProxy() {}
+
+bool RemoteSecureHardwareProvisioningProxy::shutdown() {
+  LOG(INFO) << "RemoteSecureHardwarePresentationProxy shutdown";
+  return true;
+}
+
+bool RemoteSecureHardwareProvisioningProxy::initialize(bool testCredential) {
+  LOG(INFO) << "RemoteSecureHardwareProvisioningProxy created, "
+               "sizeof(EicProvisioning): "
+            << sizeof(EicProvisioning);
+  return eicProvisioningInit(&ctx_, testCredential);
+}
+
+bool RemoteSecureHardwareProvisioningProxy::initializeForUpdate(
+    bool testCredential, string docType,
+    vector<uint8_t> encryptedCredentialKeys) {
+  return eicProvisioningInitForUpdate(
+      &ctx_, testCredential, docType.c_str(), docType.size(),
+      encryptedCredentialKeys.data(), encryptedCredentialKeys.size());
+}
+
+// Returns public key certificate.
+optional<vector<uint8_t>>
+RemoteSecureHardwareProvisioningProxy::createCredentialKey(
+    const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId) {
+  uint8_t publicKeyCert[4096];
+  size_t publicKeyCertSize = sizeof publicKeyCert;
+  if (!eicProvisioningCreateCredentialKey(
+          &ctx_, challenge.data(), challenge.size(), applicationId.data(),
+          applicationId.size(), publicKeyCert, &publicKeyCertSize)) {
+    return {};
+  }
+  vector<uint8_t> pubKeyCert(publicKeyCertSize);
+  memcpy(pubKeyCert.data(), publicKeyCert, publicKeyCertSize);
+  return pubKeyCert;
+}
+
+bool RemoteSecureHardwareProvisioningProxy::startPersonalization(
+    int accessControlProfileCount, vector<int> entryCounts,
+    const string& docType, size_t expectedProofOfProvisioningSize) {
+  if (!eicProvisioningStartPersonalization(
+          &ctx_, accessControlProfileCount, entryCounts.data(),
+          entryCounts.size(), docType.c_str(), docType.size(),
+          expectedProofOfProvisioningSize)) {
+    return false;
+  }
+  return true;
+}
+
+// Returns MAC (28 bytes).
+optional<vector<uint8_t>>
+RemoteSecureHardwareProvisioningProxy::addAccessControlProfile(
+    int id, const vector<uint8_t>& readerCertificate,
+    bool userAuthenticationRequired, uint64_t timeoutMillis,
+    uint64_t secureUserId) {
+  vector<uint8_t> mac(28);
+  uint8_t scratchSpace[512];
+  if (!eicProvisioningAddAccessControlProfile(
+          &ctx_, id, readerCertificate.data(), readerCertificate.size(),
+          userAuthenticationRequired, timeoutMillis, secureUserId, mac.data(),
+          scratchSpace, sizeof(scratchSpace))) {
+    return {};
+  }
+  return mac;
+}
+
+bool RemoteSecureHardwareProvisioningProxy::beginAddEntry(
+    const vector<int>& accessControlProfileIds, const string& nameSpace,
+    const string& name, uint64_t entrySize) {
+  uint8_t scratchSpace[512];
+  vector<uint8_t> uint8AccessControlProfileIds;
+  for (size_t i = 0; i < accessControlProfileIds.size(); i++) {
+    uint8AccessControlProfileIds.push_back(accessControlProfileIds[i] & 0xFF);
+  }
+
+  return eicProvisioningBeginAddEntry(
+      &ctx_, uint8AccessControlProfileIds.data(),
+      uint8AccessControlProfileIds.size(), nameSpace.c_str(), nameSpace.size(),
+      name.c_str(), name.size(), entrySize, scratchSpace, sizeof(scratchSpace));
+}
+
+// Returns encryptedContent.
+optional<vector<uint8_t>> RemoteSecureHardwareProvisioningProxy::addEntryValue(
+    const vector<int>& accessControlProfileIds, const string& nameSpace,
+    const string& name, const vector<uint8_t>& content) {
+  vector<uint8_t> eicEncryptedContent;
+  uint8_t scratchSpace[512];
+  vector<uint8_t> uint8AccessControlProfileIds;
+  for (size_t i = 0; i < accessControlProfileIds.size(); i++) {
+    uint8AccessControlProfileIds.push_back(accessControlProfileIds[i] & 0xFF);
+  }
+
+  eicEncryptedContent.resize(content.size() + 28);
+  if (!eicProvisioningAddEntryValue(&ctx_, uint8AccessControlProfileIds.data(),
+                                    uint8AccessControlProfileIds.size(),
+                                    nameSpace.c_str(), nameSpace.size(),
+                                    name.c_str(), name.size(), content.data(),
+                                    content.size(), eicEncryptedContent.data(),
+                                    scratchSpace, sizeof(scratchSpace))) {
+    return {};
+  }
+  return eicEncryptedContent;
+}
+
+// Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+optional<vector<uint8_t>>
+RemoteSecureHardwareProvisioningProxy::finishAddingEntries() {
+  vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+  if (!eicProvisioningFinishAddingEntries(&ctx_,
+                                          signatureOfToBeSigned.data())) {
+    return {};
+  }
+  return signatureOfToBeSigned;
+}
+
+// Returns encryptedCredentialKeys.
+optional<vector<uint8_t>>
+RemoteSecureHardwareProvisioningProxy::finishGetCredentialData(
+    const string& docType) {
+  vector<uint8_t> encryptedCredentialKeys(116);
+  size_t size = encryptedCredentialKeys.size();
+  if (!eicProvisioningFinishGetCredentialData(
+          &ctx_, docType.c_str(), docType.size(),
+          encryptedCredentialKeys.data(), &size)) {
+    return {};
+  }
+  encryptedCredentialKeys.resize(size);
+  return encryptedCredentialKeys;
+}
+
+// ----------------------------------------------------------------------
+
+RemoteSecureHardwarePresentationProxy::RemoteSecureHardwarePresentationProxy() {
+}
+
+RemoteSecureHardwarePresentationProxy::
+    ~RemoteSecureHardwarePresentationProxy() {}
+
+bool RemoteSecureHardwarePresentationProxy::initialize(
+    bool testCredential, string docType,
+    vector<uint8_t> encryptedCredentialKeys) {
+  LOG(INFO) << "RemoteSecureHardwarePresentationProxy created, "
+               "sizeof(EicPresentation): "
+            << sizeof(EicPresentation);
+  return eicPresentationInit(&ctx_, testCredential, docType.c_str(),
+                             docType.size(), encryptedCredentialKeys.data(),
+                             encryptedCredentialKeys.size());
+}
+
+// Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+optional<pair<vector<uint8_t>, vector<uint8_t>>>
+RemoteSecureHardwarePresentationProxy::generateSigningKeyPair(string docType,
+                                                              time_t now) {
+  uint8_t publicKeyCert[512];
+  size_t publicKeyCertSize = sizeof(publicKeyCert);
+  vector<uint8_t> signingKeyBlob(60);
+
+  if (!eicPresentationGenerateSigningKeyPair(
+          &ctx_, docType.c_str(), docType.size(), now, publicKeyCert,
+          &publicKeyCertSize, signingKeyBlob.data())) {
+    return {};
+  }
+
+  vector<uint8_t> cert;
+  cert.resize(publicKeyCertSize);
+  memcpy(cert.data(), publicKeyCert, publicKeyCertSize);
+
+  return std::make_pair(cert, signingKeyBlob);
+}
+
+// Returns private key
+optional<vector<uint8_t>>
+RemoteSecureHardwarePresentationProxy::createEphemeralKeyPair() {
+  vector<uint8_t> priv(EIC_P256_PRIV_KEY_SIZE);
+  if (!eicPresentationCreateEphemeralKeyPair(&ctx_, priv.data())) {
+    return {};
+  }
+  return priv;
+}
+
+optional<uint64_t>
+RemoteSecureHardwarePresentationProxy::createAuthChallenge() {
+  uint64_t challenge;
+  if (!eicPresentationCreateAuthChallenge(&ctx_, &challenge)) {
+    return {};
+  }
+  return challenge;
+}
+
+bool RemoteSecureHardwarePresentationProxy::shutdown() {
+  LOG(INFO) << "RemoteSecureHardwarePresentationProxy shutdown";
+  return true;
+}
+
+bool RemoteSecureHardwarePresentationProxy::pushReaderCert(
+    const vector<uint8_t>& certX509) {
+  return eicPresentationPushReaderCert(&ctx_, certX509.data(), certX509.size());
+}
+
+bool RemoteSecureHardwarePresentationProxy::validateRequestMessage(
+    const vector<uint8_t>& sessionTranscript,
+    const vector<uint8_t>& requestMessage, int coseSignAlg,
+    const vector<uint8_t>& readerSignatureOfToBeSigned) {
+  return eicPresentationValidateRequestMessage(
+      &ctx_, sessionTranscript.data(), sessionTranscript.size(),
+      requestMessage.data(), requestMessage.size(), coseSignAlg,
+      readerSignatureOfToBeSigned.data(), readerSignatureOfToBeSigned.size());
+}
+
+bool RemoteSecureHardwarePresentationProxy::setAuthToken(
+    uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+    int hardwareAuthenticatorType, uint64_t timeStamp,
+    const vector<uint8_t>& mac, uint64_t verificationTokenChallenge,
+    uint64_t verificationTokenTimestamp, int verificationTokenSecurityLevel,
+    const vector<uint8_t>& verificationTokenMac) {
+  return eicPresentationSetAuthToken(
+      &ctx_, challenge, secureUserId, authenticatorId,
+      hardwareAuthenticatorType, timeStamp, mac.data(), mac.size(),
+      verificationTokenChallenge, verificationTokenTimestamp,
+      verificationTokenSecurityLevel, verificationTokenMac.data(),
+      verificationTokenMac.size());
+}
+
+optional<bool>
+RemoteSecureHardwarePresentationProxy::validateAccessControlProfile(
+    int id, const vector<uint8_t>& readerCertificate,
+    bool userAuthenticationRequired, int timeoutMillis, uint64_t secureUserId,
+    const vector<uint8_t>& mac) {
+  bool accessGranted = false;
+  uint8_t scratchSpace[512];
+  if (!eicPresentationValidateAccessControlProfile(
+          &ctx_, id, readerCertificate.data(), readerCertificate.size(),
+          userAuthenticationRequired, timeoutMillis, secureUserId, mac.data(),
+          &accessGranted, scratchSpace, sizeof(scratchSpace))) {
+    return {};
+  }
+  return accessGranted;
+}
+
+bool RemoteSecureHardwarePresentationProxy::startRetrieveEntries() {
+  return eicPresentationStartRetrieveEntries(&ctx_);
+}
+
+bool RemoteSecureHardwarePresentationProxy::calcMacKey(
+    const vector<uint8_t>& sessionTranscript,
+    const vector<uint8_t>& readerEphemeralPublicKey,
+    const vector<uint8_t>& signingKeyBlob, const string& docType,
+    unsigned int numNamespacesWithValues,
+    size_t expectedProofOfProvisioningSize) {
+  if (signingKeyBlob.size() != 60) {
+    eicDebug("Unexpected size %zd of signingKeyBlob, expected 60",
+             signingKeyBlob.size());
+    return false;
+  }
+  return eicPresentationCalcMacKey(
+      &ctx_, sessionTranscript.data(), sessionTranscript.size(),
+      readerEphemeralPublicKey.data(), signingKeyBlob.data(), docType.c_str(),
+      docType.size(), numNamespacesWithValues, expectedProofOfProvisioningSize);
+}
+
+AccessCheckResult
+RemoteSecureHardwarePresentationProxy::startRetrieveEntryValue(
+    const string& nameSpace, const string& name,
+    unsigned int newNamespaceNumEntries, int32_t entrySize,
+    const vector<int32_t>& accessControlProfileIds) {
+  uint8_t scratchSpace[512];
+  vector<uint8_t> uint8AccessControlProfileIds;
+  for (size_t i = 0; i < accessControlProfileIds.size(); i++) {
+    uint8AccessControlProfileIds.push_back(accessControlProfileIds[i] & 0xFF);
+  }
+
+  EicAccessCheckResult result = eicPresentationStartRetrieveEntryValue(
+      &ctx_, nameSpace.c_str(), nameSpace.size(), name.c_str(), name.size(),
+      newNamespaceNumEntries, entrySize, uint8AccessControlProfileIds.data(),
+      uint8AccessControlProfileIds.size(), scratchSpace, sizeof(scratchSpace));
+  switch (result) {
+    case EIC_ACCESS_CHECK_RESULT_OK:
+      return AccessCheckResult::kOk;
+    case EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES:
+      return AccessCheckResult::kNoAccessControlProfiles;
+    case EIC_ACCESS_CHECK_RESULT_FAILED:
+      return AccessCheckResult::kFailed;
+    case EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED:
+      return AccessCheckResult::kUserAuthenticationFailed;
+    case EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED:
+      return AccessCheckResult::kReaderAuthenticationFailed;
+  }
+  eicDebug("Unknown result with code %d, returning kFailed", (int)result);
+  return AccessCheckResult::kFailed;
+}
+
+optional<vector<uint8_t>>
+RemoteSecureHardwarePresentationProxy::retrieveEntryValue(
+    const vector<uint8_t>& encryptedContent, const string& nameSpace,
+    const string& name, const vector<int32_t>& accessControlProfileIds) {
+  uint8_t scratchSpace[512];
+  vector<uint8_t> uint8AccessControlProfileIds;
+  for (size_t i = 0; i < accessControlProfileIds.size(); i++) {
+    uint8AccessControlProfileIds.push_back(accessControlProfileIds[i] & 0xFF);
+  }
+
+  vector<uint8_t> content;
+  content.resize(encryptedContent.size() - 28);
+  if (!eicPresentationRetrieveEntryValue(
+          &ctx_, encryptedContent.data(), encryptedContent.size(),
+          content.data(), nameSpace.c_str(), nameSpace.size(), name.c_str(),
+          name.size(), uint8AccessControlProfileIds.data(),
+          uint8AccessControlProfileIds.size(), scratchSpace,
+          sizeof(scratchSpace))) {
+    return {};
+  }
+  return content;
+}
+
+optional<vector<uint8_t>>
+RemoteSecureHardwarePresentationProxy::finishRetrieval() {
+  vector<uint8_t> mac(32);
+  size_t macSize = 32;
+  if (!eicPresentationFinishRetrieval(&ctx_, mac.data(), &macSize)) {
+    return {};
+  }
+  mac.resize(macSize);
+  return mac;
+}
+
+optional<vector<uint8_t>>
+RemoteSecureHardwarePresentationProxy::deleteCredential(
+    const string& docType, const vector<uint8_t>& challenge,
+    bool includeChallenge, size_t proofOfDeletionCborSize) {
+  vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+  if (!eicPresentationDeleteCredential(
+          &ctx_, docType.c_str(), docType.size(), challenge.data(),
+          challenge.size(), includeChallenge, proofOfDeletionCborSize,
+          signatureOfToBeSigned.data())) {
+    return {};
+  }
+  return signatureOfToBeSigned;
+}
+
+optional<vector<uint8_t>> RemoteSecureHardwarePresentationProxy::proveOwnership(
+    const string& docType, bool testCredential,
+    const vector<uint8_t>& challenge, size_t proofOfOwnershipCborSize) {
+  vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+  if (!eicPresentationProveOwnership(&ctx_, docType.c_str(), docType.size(),
+                                     testCredential, challenge.data(),
+                                     challenge.size(), proofOfOwnershipCborSize,
+                                     signatureOfToBeSigned.data())) {
+    return {};
+  }
+  return signatureOfToBeSigned;
+}
+
+}  // namespace android::hardware::identity
diff --git a/guest/hals/identity/RemoteSecureHardwareProxy.h b/guest/hals/identity/RemoteSecureHardwareProxy.h
new file mode 100644
index 0000000..39cb422
--- /dev/null
+++ b/guest/hals/identity/RemoteSecureHardwareProxy.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
+#define ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
+
+#include <libeic.h>
+
+#include "SecureHardwareProxy.h"
+
+namespace android::hardware::identity {
+
+// This implementation uses libEmbeddedIC in-process.
+//
+class RemoteSecureHardwareProvisioningProxy
+    : public SecureHardwareProvisioningProxy {
+ public:
+  RemoteSecureHardwareProvisioningProxy();
+  virtual ~RemoteSecureHardwareProvisioningProxy();
+
+  bool initialize(bool testCredential) override;
+
+  bool initializeForUpdate(bool testCredential, string docType,
+                           vector<uint8_t> encryptedCredentialKeys) override;
+
+  bool shutdown() override;
+
+  // Returns public key certificate.
+  optional<vector<uint8_t>> createCredentialKey(
+      const vector<uint8_t>& challenge,
+      const vector<uint8_t>& applicationId) override;
+
+  bool startPersonalization(int accessControlProfileCount,
+                            vector<int> entryCounts, const string& docType,
+                            size_t expectedProofOfProvisioningSize) override;
+
+  // Returns MAC (28 bytes).
+  optional<vector<uint8_t>> addAccessControlProfile(
+      int id, const vector<uint8_t>& readerCertificate,
+      bool userAuthenticationRequired, uint64_t timeoutMillis,
+      uint64_t secureUserId) override;
+
+  bool beginAddEntry(const vector<int>& accessControlProfileIds,
+                     const string& nameSpace, const string& name,
+                     uint64_t entrySize) override;
+
+  // Returns encryptedContent.
+  optional<vector<uint8_t>> addEntryValue(
+      const vector<int>& accessControlProfileIds, const string& nameSpace,
+      const string& name, const vector<uint8_t>& content) override;
+
+  // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+  optional<vector<uint8_t>> finishAddingEntries() override;
+
+  // Returns encryptedCredentialKeys (80 bytes).
+  optional<vector<uint8_t>> finishGetCredentialData(
+      const string& docType) override;
+
+ protected:
+  EicProvisioning ctx_;
+};
+
+// This implementation uses libEmbeddedIC in-process.
+//
+class RemoteSecureHardwarePresentationProxy
+    : public SecureHardwarePresentationProxy {
+ public:
+  RemoteSecureHardwarePresentationProxy();
+  virtual ~RemoteSecureHardwarePresentationProxy();
+
+  bool initialize(bool testCredential, string docType,
+                  vector<uint8_t> encryptedCredentialKeys) override;
+
+  // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+  optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(
+      string docType, time_t now) override;
+
+  // Returns private key
+  optional<vector<uint8_t>> createEphemeralKeyPair() override;
+
+  optional<uint64_t> createAuthChallenge() override;
+
+  bool startRetrieveEntries() override;
+
+  bool setAuthToken(uint64_t challenge, uint64_t secureUserId,
+                    uint64_t authenticatorId, int hardwareAuthenticatorType,
+                    uint64_t timeStamp, const vector<uint8_t>& mac,
+                    uint64_t verificationTokenChallenge,
+                    uint64_t verificationTokenTimestamp,
+                    int verificationTokenSecurityLevel,
+                    const vector<uint8_t>& verificationTokenMac) override;
+
+  bool pushReaderCert(const vector<uint8_t>& certX509) override;
+
+  optional<bool> validateAccessControlProfile(
+      int id, const vector<uint8_t>& readerCertificate,
+      bool userAuthenticationRequired, int timeoutMillis, uint64_t secureUserId,
+      const vector<uint8_t>& mac) override;
+
+  bool validateRequestMessage(
+      const vector<uint8_t>& sessionTranscript,
+      const vector<uint8_t>& requestMessage, int coseSignAlg,
+      const vector<uint8_t>& readerSignatureOfToBeSigned) override;
+
+  bool calcMacKey(const vector<uint8_t>& sessionTranscript,
+                  const vector<uint8_t>& readerEphemeralPublicKey,
+                  const vector<uint8_t>& signingKeyBlob, const string& docType,
+                  unsigned int numNamespacesWithValues,
+                  size_t expectedProofOfProvisioningSize) override;
+
+  AccessCheckResult startRetrieveEntryValue(
+      const string& nameSpace, const string& name,
+      unsigned int newNamespaceNumEntries, int32_t entrySize,
+      const vector<int32_t>& accessControlProfileIds) override;
+
+  optional<vector<uint8_t>> retrieveEntryValue(
+      const vector<uint8_t>& encryptedContent, const string& nameSpace,
+      const string& name,
+      const vector<int32_t>& accessControlProfileIds) override;
+
+  optional<vector<uint8_t>> finishRetrieval() override;
+
+  optional<vector<uint8_t>> deleteCredential(
+      const string& docType, const vector<uint8_t>& challenge,
+      bool includeChallenge, size_t proofOfDeletionCborSize) override;
+
+  optional<vector<uint8_t>> proveOwnership(
+      const string& docType, bool testCredential,
+      const vector<uint8_t>& challenge,
+      size_t proofOfOwnershipCborSize) override;
+
+  bool shutdown() override;
+
+ protected:
+  EicPresentation ctx_;
+};
+
+// Factory implementation.
+//
+class RemoteSecureHardwareProxyFactory : public SecureHardwareProxyFactory {
+ public:
+  RemoteSecureHardwareProxyFactory() {}
+  virtual ~RemoteSecureHardwareProxyFactory() {}
+
+  sp<SecureHardwareProvisioningProxy> createProvisioningProxy() override {
+    return new RemoteSecureHardwareProvisioningProxy();
+  }
+
+  sp<SecureHardwarePresentationProxy> createPresentationProxy() override {
+    return new RemoteSecureHardwarePresentationProxy();
+  }
+};
+
+}  // namespace android::hardware::identity
+
+#endif  // ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
diff --git a/guest/hals/identity/android.hardware.identity-service.remote.rc b/guest/hals/identity/android.hardware.identity-service.remote.rc
new file mode 100644
index 0000000..e1dc7a9
--- /dev/null
+++ b/guest/hals/identity/android.hardware.identity-service.remote.rc
@@ -0,0 +1,3 @@
+service vendor.identity-remote /vendor/bin/hw/android.hardware.identity-service.remote
+    class hal
+    user nobody
diff --git a/guest/hals/identity/android.hardware.identity-service.remote.xml b/guest/hals/identity/android.hardware.identity-service.remote.xml
new file mode 100644
index 0000000..a074250
--- /dev/null
+++ b/guest/hals/identity/android.hardware.identity-service.remote.xml
@@ -0,0 +1,10 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.identity</name>
+        <version>3</version>
+        <interface>
+            <name>IIdentityCredentialStore</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+</manifest>
diff --git a/guest/hals/identity/android.hardware.identity_credential.remote.xml b/guest/hals/identity/android.hardware.identity_credential.remote.xml
new file mode 100644
index 0000000..5149792
--- /dev/null
+++ b/guest/hals/identity/android.hardware.identity_credential.remote.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<permissions>
+  <feature name="android.hardware.identity_credential" version="202101" />
+</permissions>
diff --git a/guest/hals/identity/common/IdentityCredential.cpp b/guest/hals/identity/common/IdentityCredential.cpp
new file mode 100644
index 0000000..3555c53
--- /dev/null
+++ b/guest/hals/identity/common/IdentityCredential.cpp
@@ -0,0 +1,945 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "IdentityCredential"
+
+#include "IdentityCredential.h"
+#include "IdentityCredentialStore.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <string.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include "SecureHardwareProxy.h"
+#include "WritableIdentityCredential.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::aidl::android::hardware::keymaster::Timestamp;
+using ::android::base::StringPrintf;
+using ::std::optional;
+
+using namespace ::android::hardware::identity;
+
+int IdentityCredential::initialize() {
+  if (credentialData_.size() == 0) {
+    LOG(ERROR) << "CredentialData is empty";
+    return IIdentityCredentialStore::STATUS_INVALID_DATA;
+  }
+  auto [item, _, message] = cppbor::parse(credentialData_);
+  if (item == nullptr) {
+    LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
+    return IIdentityCredentialStore::STATUS_INVALID_DATA;
+  }
+
+  const cppbor::Array* arrayItem = item->asArray();
+  if (arrayItem == nullptr || arrayItem->size() != 3) {
+    LOG(ERROR) << "CredentialData is not an array with three elements";
+    return IIdentityCredentialStore::STATUS_INVALID_DATA;
+  }
+
+  const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
+  const cppbor::Bool* testCredentialItem =
+      ((*arrayItem)[1]->asSimple() != nullptr
+           ? ((*arrayItem)[1]->asSimple()->asBool())
+           : nullptr);
+  const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
+  if (docTypeItem == nullptr || testCredentialItem == nullptr ||
+      encryptedCredentialKeysItem == nullptr) {
+    LOG(ERROR) << "CredentialData unexpected item types";
+    return IIdentityCredentialStore::STATUS_INVALID_DATA;
+  }
+
+  docType_ = docTypeItem->value();
+  testCredential_ = testCredentialItem->value();
+
+  encryptedCredentialKeys_ = encryptedCredentialKeysItem->value();
+  if (!hwProxy_->initialize(testCredential_, docType_,
+                            encryptedCredentialKeys_)) {
+    LOG(ERROR) << "hwProxy->initialize failed";
+    return false;
+  }
+
+  return IIdentityCredentialStore::STATUS_OK;
+}
+
+ndk::ScopedAStatus IdentityCredential::deleteCredential(
+    vector<uint8_t>* outProofOfDeletionSignature) {
+  return deleteCredentialCommon({}, false, outProofOfDeletionSignature);
+}
+
+ndk::ScopedAStatus IdentityCredential::deleteCredentialWithChallenge(
+    const vector<uint8_t>& challenge,
+    vector<uint8_t>* outProofOfDeletionSignature) {
+  return deleteCredentialCommon(challenge, true, outProofOfDeletionSignature);
+}
+
+ndk::ScopedAStatus IdentityCredential::deleteCredentialCommon(
+    const vector<uint8_t>& challenge, bool includeChallenge,
+    vector<uint8_t>* outProofOfDeletionSignature) {
+  if (challenge.size() > 32) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
+  }
+
+  cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
+  if (includeChallenge) {
+    array = {"ProofOfDeletion", docType_, challenge, testCredential_};
+  }
+
+  vector<uint8_t> proofOfDeletionCbor = array.encode();
+  vector<uint8_t> podDigest = support::sha256(proofOfDeletionCbor);
+
+  optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->deleteCredential(
+      docType_, challenge, includeChallenge, proofOfDeletionCbor.size());
+  if (!signatureOfToBeSigned) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "Error signing ProofOfDeletion"));
+  }
+
+  optional<vector<uint8_t>> signature =
+      support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+                                          proofOfDeletionCbor,  // data
+                                          {});  // certificateChain
+  if (!signature) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
+  }
+
+  *outProofOfDeletionSignature = signature.value();
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::proveOwnership(
+    const vector<uint8_t>& challenge,
+    vector<uint8_t>* outProofOfOwnershipSignature) {
+  if (challenge.size() > 32) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
+  }
+
+  cppbor::Array array;
+  array = {"ProofOfOwnership", docType_, challenge, testCredential_};
+  vector<uint8_t> proofOfOwnershipCbor = array.encode();
+  vector<uint8_t> podDigest = support::sha256(proofOfOwnershipCbor);
+
+  optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->proveOwnership(
+      docType_, testCredential_, challenge, proofOfOwnershipCbor.size());
+  if (!signatureOfToBeSigned) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "Error signing ProofOfOwnership"));
+  }
+
+  optional<vector<uint8_t>> signature =
+      support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+                                          proofOfOwnershipCbor,  // data
+                                          {});  // certificateChain
+  if (!signature) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
+  }
+
+  *outProofOfOwnershipSignature = signature.value();
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(
+    vector<uint8_t>* outKeyPair) {
+  optional<vector<uint8_t>> ephemeralPriv = hwProxy_->createEphemeralKeyPair();
+  if (!ephemeralPriv) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "Error creating ephemeral key"));
+  }
+  optional<vector<uint8_t>> keyPair =
+      support::ecPrivateKeyToKeyPair(ephemeralPriv.value());
+  if (!keyPair) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "Error creating ephemeral key-pair"));
+  }
+
+  // Stash public key of this key-pair for later check in startRetrieval().
+  optional<vector<uint8_t>> publicKey =
+      support::ecKeyPairGetPublicKey(keyPair.value());
+  if (!publicKey) {
+    LOG(ERROR) << "Error getting public part of ephemeral key pair";
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "Error getting public part of ephemeral key pair"));
+  }
+  ephemeralPublicKey_ = publicKey.value();
+
+  *outKeyPair = keyPair.value();
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey(
+    const vector<uint8_t>& publicKey) {
+  readerPublicKey_ = publicKey;
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::createAuthChallenge(
+    int64_t* outChallenge) {
+  optional<uint64_t> challenge = hwProxy_->createAuthChallenge();
+  if (!challenge) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED, "Error generating challenge"));
+  }
+  *outChallenge = challenge.value();
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
+    const vector<RequestNamespace>& requestNamespaces) {
+  requestNamespaces_ = requestNamespaces;
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::setVerificationToken(
+    const VerificationToken& verificationToken) {
+  verificationToken_ = verificationToken;
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::startRetrieval(
+    const vector<SecureAccessControlProfile>& accessControlProfiles,
+    const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
+    const vector<uint8_t>& signingKeyBlob,
+    const vector<uint8_t>& sessionTranscript,
+    const vector<uint8_t>& readerSignature,
+    const vector<int32_t>& requestCounts) {
+  std::unique_ptr<cppbor::Item> sessionTranscriptItem;
+  if (sessionTranscript.size() > 0) {
+    auto [item, _, message] = cppbor::parse(sessionTranscript);
+    if (item == nullptr) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "SessionTranscript contains invalid CBOR"));
+    }
+    sessionTranscriptItem = std::move(item);
+  }
+  if (numStartRetrievalCalls_ > 0) {
+    if (sessionTranscript_ != sessionTranscript) {
+      LOG(ERROR) << "Session Transcript changed";
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
+          "Passed-in SessionTranscript doesn't match previously used "
+          "SessionTranscript"));
+    }
+  }
+  sessionTranscript_ = sessionTranscript;
+
+  // This resets various state in the TA...
+  if (!hwProxy_->startRetrieveEntries()) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "Error starting retrieving entries"));
+  }
+
+  optional<vector<uint8_t>> signatureOfToBeSigned;
+  if (readerSignature.size() > 0) {
+    signatureOfToBeSigned = support::coseSignGetSignature(readerSignature);
+    if (!signatureOfToBeSigned) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+          "Error extracting signatureOfToBeSigned from COSE_Sign1"));
+    }
+  }
+
+  // Feed the auth token to secure hardware only if they're valid.
+  if (authToken.timestamp.milliSeconds != 0) {
+    if (!hwProxy_->setAuthToken(
+            authToken.challenge, authToken.userId, authToken.authenticatorId,
+            int(authToken.authenticatorType), authToken.timestamp.milliSeconds,
+            authToken.mac, verificationToken_.challenge,
+            verificationToken_.timestamp.milliSeconds,
+            int(verificationToken_.securityLevel), verificationToken_.mac)) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA, "Invalid Auth Token"));
+    }
+  }
+
+  // We'll be feeding ACPs interleaved with certificates from the reader
+  // certificate chain...
+  vector<SecureAccessControlProfile> remainingAcps = accessControlProfiles;
+
+  // ... and we'll use those ACPs to build up a 32-bit mask indicating which
+  // of the possible 32 ACPs grants access.
+  uint32_t accessControlProfileMask = 0;
+
+  // If there is a signature, validate that it was made with the top-most key in
+  // the certificate chain embedded in the COSE_Sign1 structure.
+  optional<vector<uint8_t>> readerCertificateChain;
+  if (readerSignature.size() > 0) {
+    readerCertificateChain = support::coseSignGetX5Chain(readerSignature);
+    if (!readerCertificateChain) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+          "Unable to get reader certificate chain from COSE_Sign1"));
+    }
+
+    // First, feed all the reader certificates to the secure hardware. We start
+    // at the end..
+    optional<vector<vector<uint8_t>>> splitCerts =
+        support::certificateChainSplit(readerCertificateChain.value());
+    if (!splitCerts || splitCerts.value().size() == 0) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+          "Error splitting certificate chain from COSE_Sign1"));
+    }
+    for (ssize_t n = splitCerts.value().size() - 1; n >= 0; --n) {
+      const vector<uint8_t>& x509Cert = splitCerts.value()[n];
+      if (!hwProxy_->pushReaderCert(x509Cert)) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+            IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+            StringPrintf("Error validating reader certificate %zd", n)
+                .c_str()));
+      }
+
+      // If we have ACPs for that particular certificate, send them to the
+      // TA right now...
+      //
+      // Remember in this case certificate equality is done by comparing public
+      // keys, not bitwise comparison of the certificates.
+      //
+      optional<vector<uint8_t>> x509CertPubKey =
+          support::certificateChainGetTopMostKey(x509Cert);
+      if (!x509CertPubKey) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+            IIdentityCredentialStore::STATUS_FAILED,
+            StringPrintf("Error getting public key from reader certificate %zd",
+                         n)
+                .c_str()));
+      }
+      vector<SecureAccessControlProfile>::iterator it = remainingAcps.begin();
+      while (it != remainingAcps.end()) {
+        const SecureAccessControlProfile& profile = *it;
+        if (profile.readerCertificate.encodedCertificate.size() == 0) {
+          ++it;
+          continue;
+        }
+        optional<vector<uint8_t>> profilePubKey =
+            support::certificateChainGetTopMostKey(
+                profile.readerCertificate.encodedCertificate);
+        if (!profilePubKey) {
+          return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+              IIdentityCredentialStore::STATUS_FAILED,
+              "Error getting public key from profile"));
+        }
+        if (profilePubKey.value() == x509CertPubKey.value()) {
+          optional<bool> res = hwProxy_->validateAccessControlProfile(
+              profile.id, profile.readerCertificate.encodedCertificate,
+              profile.userAuthenticationRequired, profile.timeoutMillis,
+              profile.secureUserId, profile.mac);
+          if (!res) {
+            return ndk::ScopedAStatus(
+                AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "Error validating access control profile"));
+          }
+          if (res.value()) {
+            accessControlProfileMask |= (1 << profile.id);
+          }
+          it = remainingAcps.erase(it);
+        } else {
+          ++it;
+        }
+      }
+    }
+
+    // ... then pass the request message and have the TA check it's signed by
+    // the key in last certificate we pushed.
+    if (sessionTranscript.size() > 0 && itemsRequest.size() > 0 &&
+        readerSignature.size() > 0) {
+      optional<vector<uint8_t>> tbsSignature =
+          support::coseSignGetSignature(readerSignature);
+      if (!tbsSignature) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+            IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+            "Error extracting toBeSigned from COSE_Sign1"));
+      }
+      optional<int> coseSignAlg = support::coseSignGetAlg(readerSignature);
+      if (!coseSignAlg) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+            IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+            "Error extracting signature algorithm from COSE_Sign1"));
+      }
+      if (!hwProxy_->validateRequestMessage(sessionTranscript, itemsRequest,
+                                            coseSignAlg.value(),
+                                            tbsSignature.value())) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+            IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+            "readerMessage is not signed by top-level certificate"));
+      }
+    }
+  }
+
+  // Feed remaining access control profiles...
+  for (const SecureAccessControlProfile& profile : remainingAcps) {
+    optional<bool> res = hwProxy_->validateAccessControlProfile(
+        profile.id, profile.readerCertificate.encodedCertificate,
+        profile.userAuthenticationRequired, profile.timeoutMillis,
+        profile.secureUserId, profile.mac);
+    if (!res) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "Error validating access control profile"));
+    }
+    if (res.value()) {
+      accessControlProfileMask |= (1 << profile.id);
+    }
+  }
+
+  // TODO: move this check to the TA
+#if 1
+  // To prevent replay-attacks, we check that the public part of the ephemeral
+  // key we previously created, is present in the DeviceEngagement part of
+  // SessionTranscript as a COSE_Key, in uncompressed form.
+  //
+  // We do this by just searching for the X and Y coordinates.
+  if (sessionTranscript.size() > 0) {
+    auto [getXYSuccess, ePubX, ePubY] =
+        support::ecPublicKeyGetXandY(ephemeralPublicKey_);
+    if (!getXYSuccess) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+          "Error extracting X and Y from ePub"));
+    }
+    if (sessionTranscript.size() > 0 &&
+        !(memmem(sessionTranscript.data(), sessionTranscript.size(),
+                 ePubX.data(), ePubX.size()) != nullptr &&
+          memmem(sessionTranscript.data(), sessionTranscript.size(),
+                 ePubY.data(), ePubY.size()) != nullptr)) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+          "Did not find ephemeral public key's X and Y coordinates in "
+          "SessionTranscript (make sure leading zeroes are not used)"));
+    }
+  }
+#endif
+
+  // itemsRequest: If non-empty, contains request data that may be signed by the
+  // reader.  The content can be defined in the way appropriate for the
+  // credential, but there are three requirements that must be met to work with
+  // this HAL:
+  if (itemsRequest.size() > 0) {
+    // 1. The content must be a CBOR-encoded structure.
+    auto [item, _, message] = cppbor::parse(itemsRequest);
+    if (item == nullptr) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+          "Error decoding CBOR in itemsRequest"));
+    }
+
+    // 2. The CBOR structure must be a map.
+    const cppbor::Map* map = item->asMap();
+    if (map == nullptr) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+          "itemsRequest is not a CBOR map"));
+    }
+
+    // 3. The map must contain a key "nameSpaces" whose value contains a map, as
+    // described in
+    //    the example below.
+    //
+    //   NameSpaces = {
+    //     + NameSpace => DataElements ; Requested data elements for each
+    //     NameSpace
+    //   }
+    //
+    //   NameSpace = tstr
+    //
+    //   DataElements = {
+    //     + DataElement => IntentToRetain
+    //   }
+    //
+    //   DataElement = tstr
+    //   IntentToRetain = bool
+    //
+    // Here's an example of an |itemsRequest| CBOR value satisfying above
+    // requirements 1. through 3.:
+    //
+    //    {
+    //        'docType' : 'org.iso.18013-5.2019',
+    //        'nameSpaces' : {
+    //            'org.iso.18013-5.2019' : {
+    //                'Last name' : false,
+    //                'Birth date' : false,
+    //                'First name' : false,
+    //                'Home address' : true
+    //            },
+    //            'org.aamva.iso.18013-5.2019' : {
+    //                'Real Id' : false
+    //            }
+    //        }
+    //    }
+    //
+    const cppbor::Map* nsMap = nullptr;
+    for (size_t n = 0; n < map->size(); n++) {
+      const auto& [keyItem, valueItem] = (*map)[n];
+      if (keyItem->type() == cppbor::TSTR &&
+          keyItem->asTstr()->value() == "nameSpaces" &&
+          valueItem->type() == cppbor::MAP) {
+        nsMap = valueItem->asMap();
+        break;
+      }
+    }
+    if (nsMap == nullptr) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+          "No nameSpaces map in top-most map"));
+    }
+
+    for (size_t n = 0; n < nsMap->size(); n++) {
+      auto& [nsKeyItem, nsValueItem] = (*nsMap)[n];
+      const cppbor::Tstr* nsKey = nsKeyItem->asTstr();
+      const cppbor::Map* nsInnerMap = nsValueItem->asMap();
+      if (nsKey == nullptr || nsInnerMap == nullptr) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+            IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+            "Type mismatch in nameSpaces map"));
+      }
+      string requestedNamespace = nsKey->value();
+      set<string> requestedKeys;
+      for (size_t m = 0; m < nsInnerMap->size(); m++) {
+        const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
+        const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
+        const cppbor::Simple* simple = innerMapValueItem->asSimple();
+        const cppbor::Bool* intentToRetainItem =
+            (simple != nullptr) ? simple->asBool() : nullptr;
+        if (nameItem == nullptr || intentToRetainItem == nullptr) {
+          return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+              IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+              "Type mismatch in value in nameSpaces map"));
+        }
+        requestedKeys.insert(nameItem->value());
+      }
+      requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
+    }
+  }
+
+  deviceNameSpacesMap_ = cppbor::Map();
+  currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
+
+  requestCountsRemaining_ = requestCounts;
+  currentNameSpace_ = "";
+
+  itemsRequest_ = itemsRequest;
+  signingKeyBlob_ = signingKeyBlob;
+
+  // calculate the size of DeviceNameSpaces. We need to know it ahead of time.
+  calcDeviceNameSpacesSize(accessControlProfileMask);
+
+  // Count the number of non-empty namespaces
+  size_t numNamespacesWithValues = 0;
+  for (size_t n = 0; n < expectedNumEntriesPerNamespace_.size(); n++) {
+    if (expectedNumEntriesPerNamespace_[n] > 0) {
+      numNamespacesWithValues += 1;
+    }
+  }
+
+  // Finally, pass info so the HMAC key can be derived and the TA can start
+  // creating the DeviceNameSpaces CBOR...
+  if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 &&
+      signingKeyBlob.size() > 0) {
+    // We expect the reader ephemeral public key to be same size and curve
+    // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
+    // won't work. So its length should be 65 bytes and it should be
+    // starting with 0x04.
+    if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_FAILED,
+          "Reader public key is not in expected format"));
+    }
+    vector<uint8_t> pubKeyP256(readerPublicKey_.begin() + 1,
+                               readerPublicKey_.end());
+    if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob,
+                              docType_, numNamespacesWithValues,
+                              expectedDeviceNameSpacesSize_)) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_FAILED,
+          "Error starting retrieving entries"));
+    }
+  }
+
+  numStartRetrievalCalls_ += 1;
+  return ndk::ScopedAStatus::ok();
+}
+
+size_t cborNumBytesForLength(size_t length) {
+  if (length < 24) {
+    return 0;
+  } else if (length <= 0xff) {
+    return 1;
+  } else if (length <= 0xffff) {
+    return 2;
+  } else if (length <= 0xffffffff) {
+    return 4;
+  }
+  return 8;
+}
+
+size_t cborNumBytesForTstr(const string& value) {
+  return 1 + cborNumBytesForLength(value.size()) + value.size();
+}
+
+void IdentityCredential::calcDeviceNameSpacesSize(
+    uint32_t accessControlProfileMask) {
+  /*
+   * This is how DeviceNameSpaces is defined:
+   *
+   *        DeviceNameSpaces = {
+   *            * NameSpace => DeviceSignedItems
+   *        }
+   *        DeviceSignedItems = {
+   *            + DataItemName => DataItemValue
+   *        }
+   *
+   *        Namespace = tstr
+   *        DataItemName = tstr
+   *        DataItemValue = any
+   *
+   * This function will calculate its length using knowledge of how CBOR is
+   * encoded.
+   */
+  size_t ret = 0;
+  vector<unsigned int> numEntriesPerNamespace;
+  for (const RequestNamespace& rns : requestNamespaces_) {
+    vector<RequestDataItem> itemsToInclude;
+
+    for (const RequestDataItem& rdi : rns.items) {
+      // If we have a CBOR request message, skip if item isn't in it
+      if (itemsRequest_.size() > 0) {
+        const auto& it = requestedNameSpacesAndNames_.find(rns.namespaceName);
+        if (it == requestedNameSpacesAndNames_.end()) {
+          continue;
+        }
+        const set<string>& dataItemNames = it->second;
+        if (dataItemNames.find(rdi.name) == dataItemNames.end()) {
+          continue;
+        }
+      }
+
+      // Access is granted if at least one of the profiles grants access.
+      //
+      // If an item is configured without any profiles, access is denied.
+      //
+      bool authorized = false;
+      for (auto id : rdi.accessControlProfileIds) {
+        if (accessControlProfileMask & (1 << id)) {
+          authorized = true;
+          break;
+        }
+      }
+      if (!authorized) {
+        continue;
+      }
+
+      itemsToInclude.push_back(rdi);
+    }
+
+    numEntriesPerNamespace.push_back(itemsToInclude.size());
+
+    // If no entries are to be in the namespace, we don't include it in
+    // the CBOR...
+    if (itemsToInclude.size() == 0) {
+      continue;
+    }
+
+    // Key: NameSpace
+    ret += cborNumBytesForTstr(rns.namespaceName);
+
+    // Value: Open the DeviceSignedItems map
+    ret += 1 + cborNumBytesForLength(itemsToInclude.size());
+
+    for (const RequestDataItem& item : itemsToInclude) {
+      // Key: DataItemName
+      ret += cborNumBytesForTstr(item.name);
+
+      // Value: DataItemValue - entryData.size is the length of serialized CBOR
+      // so we use that.
+      ret += item.size;
+    }
+  }
+
+  // Now that we know the number of namespaces with values, we know how many
+  // bytes the DeviceNamespaces map in the beginning is going to take up.
+  ret += 1 + cborNumBytesForLength(numEntriesPerNamespace.size());
+
+  expectedDeviceNameSpacesSize_ = ret;
+  expectedNumEntriesPerNamespace_ = numEntriesPerNamespace;
+}
+
+ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
+    const string& nameSpace, const string& name, int32_t entrySize,
+    const vector<int32_t>& accessControlProfileIds) {
+  if (name.empty()) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
+  }
+  if (nameSpace.empty()) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "Name space cannot be empty"));
+  }
+
+  if (requestCountsRemaining_.size() == 0) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "No more name spaces left to go through"));
+  }
+
+  bool newNamespace;
+  if (currentNameSpace_ == "") {
+    // First call.
+    currentNameSpace_ = nameSpace;
+    newNamespace = true;
+  }
+
+  if (nameSpace == currentNameSpace_) {
+    // Same namespace.
+    if (requestCountsRemaining_[0] == 0) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "No more entries to be retrieved in current name space"));
+    }
+    requestCountsRemaining_[0] -= 1;
+  } else {
+    // New namespace.
+    if (requestCountsRemaining_[0] != 0) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "Moved to new name space but one or more entries need to be "
+          "retrieved "
+          "in current name space"));
+    }
+    if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
+      deviceNameSpacesMap_.add(currentNameSpace_,
+                               std::move(currentNameSpaceDeviceNameSpacesMap_));
+    }
+    currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
+
+    requestCountsRemaining_.erase(requestCountsRemaining_.begin());
+    currentNameSpace_ = nameSpace;
+    newNamespace = true;
+  }
+
+  // It's permissible to have an empty itemsRequest... but if non-empty you can
+  // only request what was specified in said itemsRequest. Enforce that.
+  if (itemsRequest_.size() > 0) {
+    const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
+    if (it == requestedNameSpacesAndNames_.end()) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
+          "Name space was not requested in startRetrieval"));
+    }
+    const set<string>& dataItemNames = it->second;
+    if (dataItemNames.find(name) == dataItemNames.end()) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
+          "Data item name in name space was not requested in startRetrieval"));
+    }
+  }
+
+  unsigned int newNamespaceNumEntries = 0;
+  if (newNamespace) {
+    if (expectedNumEntriesPerNamespace_.size() == 0) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "No more populated name spaces left to go through"));
+    }
+    newNamespaceNumEntries = expectedNumEntriesPerNamespace_[0];
+    expectedNumEntriesPerNamespace_.erase(
+        expectedNumEntriesPerNamespace_.begin());
+  }
+
+  // Access control is enforced in the secure hardware.
+  //
+  // ... except for STATUS_NOT_IN_REQUEST_MESSAGE, that's handled above (TODO:
+  // consolidate).
+  //
+  AccessCheckResult res =
+      hwProxy_->startRetrieveEntryValue(nameSpace, name, newNamespaceNumEntries,
+                                        entrySize, accessControlProfileIds);
+  switch (res) {
+    case AccessCheckResult::kOk:
+      /* Do nothing. */
+      break;
+    case AccessCheckResult::kFailed:
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_FAILED,
+          "Access control check failed (failed)"));
+      break;
+    case AccessCheckResult::kNoAccessControlProfiles:
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES,
+          "Access control check failed (no access control profiles)"));
+      break;
+    case AccessCheckResult::kUserAuthenticationFailed:
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED,
+          "Access control check failed (user auth)"));
+      break;
+    case AccessCheckResult::kReaderAuthenticationFailed:
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED,
+          "Access control check failed (reader auth)"));
+      break;
+  }
+
+  currentName_ = name;
+  currentAccessControlProfileIds_ = accessControlProfileIds;
+  entryRemainingBytes_ = entrySize;
+  entryValue_.resize(0);
+
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(
+    const vector<uint8_t>& encryptedContent, vector<uint8_t>* outContent) {
+  optional<vector<uint8_t>> content = hwProxy_->retrieveEntryValue(
+      encryptedContent, currentNameSpace_, currentName_,
+      currentAccessControlProfileIds_);
+  if (!content) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "Error decrypting data"));
+  }
+
+  size_t chunkSize = content.value().size();
+
+  if (chunkSize > entryRemainingBytes_) {
+    LOG(ERROR) << "Retrieved chunk of size " << chunkSize
+               << " is bigger than remaining space of size "
+               << entryRemainingBytes_;
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "Retrieved chunk is bigger than remaining space"));
+  }
+
+  entryRemainingBytes_ -= chunkSize;
+  if (entryRemainingBytes_ > 0) {
+    if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "Retrieved non-final chunk of size which isn't kGcmChunkSize"));
+    }
+  }
+
+  entryValue_.insert(entryValue_.end(), content.value().begin(),
+                     content.value().end());
+
+  if (entryRemainingBytes_ == 0) {
+    auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
+    if (entryValueItem == nullptr) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "Retrieved data which is invalid CBOR"));
+    }
+    currentNameSpaceDeviceNameSpacesMap_.add(currentName_,
+                                             std::move(entryValueItem));
+  }
+
+  *outContent = content.value();
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::finishRetrieval(
+    vector<uint8_t>* outMac, vector<uint8_t>* outDeviceNameSpaces) {
+  if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
+    deviceNameSpacesMap_.add(currentNameSpace_,
+                             std::move(currentNameSpaceDeviceNameSpacesMap_));
+  }
+  vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
+
+  if (encodedDeviceNameSpaces.size() != expectedDeviceNameSpacesSize_) {
+    LOG(ERROR) << "encodedDeviceNameSpaces is "
+               << encodedDeviceNameSpaces.size() << " bytes, "
+               << "was expecting " << expectedDeviceNameSpacesSize_;
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        StringPrintf("Unexpected CBOR size %zd for encodedDeviceNameSpaces, "
+                     "was expecting %zd",
+                     encodedDeviceNameSpaces.size(),
+                     expectedDeviceNameSpacesSize_)
+            .c_str()));
+  }
+
+  // If there's no signing key or no sessionTranscript or no reader ephemeral
+  // public key, we return the empty MAC.
+  optional<vector<uint8_t>> mac;
+  if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
+      readerPublicKey_.size() > 0) {
+    optional<vector<uint8_t>> digestToBeMaced = hwProxy_->finishRetrieval();
+    if (!digestToBeMaced || digestToBeMaced.value().size() != 32) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "Error generating digestToBeMaced"));
+    }
+    // Now construct COSE_Mac0 from the returned MAC...
+    mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */);
+  }
+
+  *outMac = mac.value_or(vector<uint8_t>({}));
+  *outDeviceNameSpaces = encodedDeviceNameSpaces;
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
+    vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
+  time_t now = time(NULL);
+  optional<pair<vector<uint8_t>, vector<uint8_t>>> pair =
+      hwProxy_->generateSigningKeyPair(docType_, now);
+  if (!pair) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
+  }
+
+  *outSigningKeyCertificate = Certificate();
+  outSigningKeyCertificate->encodedCertificate = pair->first;
+
+  *outSigningKeyBlob = pair->second;
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::updateCredential(
+    shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
+  sp<SecureHardwareProvisioningProxy> hwProxy =
+      hwProxyFactory_->createProvisioningProxy();
+  shared_ptr<WritableIdentityCredential> wc =
+      ndk::SharedRefBase::make<WritableIdentityCredential>(hwProxy, docType_,
+                                                           testCredential_);
+  if (!wc->initializeForUpdate(encryptedCredentialKeys_)) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "Error initializing WritableIdentityCredential for update"));
+  }
+  *outWritableCredential = wc;
+  return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace aidl::android::hardware::identity
diff --git a/guest/hals/identity/common/IdentityCredential.h b/guest/hals/identity/common/IdentityCredential.h
new file mode 100644
index 0000000..aaf772a
--- /dev/null
+++ b/guest/hals/identity/common/IdentityCredential.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H
+#define ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H
+
+#include <aidl/android/hardware/identity/BnIdentityCredential.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <cppbor.h>
+
+#include "IdentityCredentialStore.h"
+#include "SecureHardwareProxy.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::aidl::android::hardware::keymaster::HardwareAuthToken;
+using ::aidl::android::hardware::keymaster::VerificationToken;
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwarePresentationProxy;
+using ::std::map;
+using ::std::set;
+using ::std::string;
+using ::std::vector;
+
+class IdentityCredential : public BnIdentityCredential {
+ public:
+  IdentityCredential(sp<SecureHardwareProxyFactory> hwProxyFactory,
+                     sp<SecureHardwarePresentationProxy> hwProxy,
+                     const vector<uint8_t>& credentialData)
+      : hwProxyFactory_(hwProxyFactory),
+        hwProxy_(hwProxy),
+        credentialData_(credentialData),
+        numStartRetrievalCalls_(0),
+        expectedDeviceNameSpacesSize_(0) {}
+
+  // Parses and decrypts credentialData_, return a status code from
+  // IIdentityCredentialStore. Must be called right after construction.
+  int initialize();
+
+  // Methods from IIdentityCredential follow.
+  ndk::ScopedAStatus deleteCredential(
+      vector<uint8_t>* outProofOfDeletionSignature) override;
+  ndk::ScopedAStatus deleteCredentialWithChallenge(
+      const vector<uint8_t>& challenge,
+      vector<uint8_t>* outProofOfDeletionSignature) override;
+  ndk::ScopedAStatus proveOwnership(
+      const vector<uint8_t>& challenge,
+      vector<uint8_t>* outProofOfOwnershipSignature) override;
+  ndk::ScopedAStatus createEphemeralKeyPair(
+      vector<uint8_t>* outKeyPair) override;
+  ndk::ScopedAStatus setReaderEphemeralPublicKey(
+      const vector<uint8_t>& publicKey) override;
+  ndk::ScopedAStatus createAuthChallenge(int64_t* outChallenge) override;
+  ndk::ScopedAStatus setRequestedNamespaces(
+      const vector<RequestNamespace>& requestNamespaces) override;
+  ndk::ScopedAStatus setVerificationToken(
+      const VerificationToken& verificationToken) override;
+  ndk::ScopedAStatus startRetrieval(
+      const vector<SecureAccessControlProfile>& accessControlProfiles,
+      const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
+      const vector<uint8_t>& signingKeyBlob,
+      const vector<uint8_t>& sessionTranscript,
+      const vector<uint8_t>& readerSignature,
+      const vector<int32_t>& requestCounts) override;
+  ndk::ScopedAStatus startRetrieveEntryValue(
+      const string& nameSpace, const string& name, int32_t entrySize,
+      const vector<int32_t>& accessControlProfileIds) override;
+  ndk::ScopedAStatus retrieveEntryValue(const vector<uint8_t>& encryptedContent,
+                                        vector<uint8_t>* outContent) override;
+  ndk::ScopedAStatus finishRetrieval(
+      vector<uint8_t>* outMac, vector<uint8_t>* outDeviceNameSpaces) override;
+  ndk::ScopedAStatus generateSigningKeyPair(
+      vector<uint8_t>* outSigningKeyBlob,
+      Certificate* outSigningKeyCertificate) override;
+
+  ndk::ScopedAStatus updateCredential(
+      shared_ptr<IWritableIdentityCredential>* outWritableCredential) override;
+
+ private:
+  ndk::ScopedAStatus deleteCredentialCommon(
+      const vector<uint8_t>& challenge, bool includeChallenge,
+      vector<uint8_t>* outProofOfDeletionSignature);
+
+  // Set by constructor
+  sp<SecureHardwareProxyFactory> hwProxyFactory_;
+  sp<SecureHardwarePresentationProxy> hwProxy_;
+  vector<uint8_t> credentialData_;
+  int numStartRetrievalCalls_;
+
+  // Set by initialize()
+  string docType_;
+  bool testCredential_;
+  vector<uint8_t> encryptedCredentialKeys_;
+
+  // Set by createEphemeralKeyPair()
+  vector<uint8_t> ephemeralPublicKey_;
+
+  // Set by setReaderEphemeralPublicKey()
+  vector<uint8_t> readerPublicKey_;
+
+  // Set by setRequestedNamespaces()
+  vector<RequestNamespace> requestNamespaces_;
+
+  // Set by setVerificationToken().
+  VerificationToken verificationToken_;
+
+  // Set at startRetrieval() time.
+  vector<uint8_t> signingKeyBlob_;
+  vector<uint8_t> sessionTranscript_;
+  vector<uint8_t> itemsRequest_;
+  vector<int32_t> requestCountsRemaining_;
+  map<string, set<string>> requestedNameSpacesAndNames_;
+  cppbor::Map deviceNameSpacesMap_;
+  cppbor::Map currentNameSpaceDeviceNameSpacesMap_;
+
+  // Calculated at startRetrieval() time.
+  size_t expectedDeviceNameSpacesSize_;
+  vector<unsigned int> expectedNumEntriesPerNamespace_;
+
+  // Set at startRetrieveEntryValue() time.
+  string currentNameSpace_;
+  string currentName_;
+  vector<int32_t> currentAccessControlProfileIds_;
+  size_t entryRemainingBytes_;
+  vector<uint8_t> entryValue_;
+
+  void calcDeviceNameSpacesSize(uint32_t accessControlProfileMask);
+};
+
+}  // namespace aidl::android::hardware::identity
+
+#endif  // ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H
diff --git a/guest/hals/identity/common/IdentityCredentialStore.cpp b/guest/hals/identity/common/IdentityCredentialStore.cpp
new file mode 100644
index 0000000..0847c0c
--- /dev/null
+++ b/guest/hals/identity/common/IdentityCredentialStore.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "IdentityCredentialStore"
+
+#include <android-base/logging.h>
+
+#include "IdentityCredential.h"
+#include "IdentityCredentialStore.h"
+#include "WritableIdentityCredential.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::aidl::android::hardware::security::keymint::
+    IRemotelyProvisionedComponent;
+
+ndk::ScopedAStatus IdentityCredentialStore::getHardwareInformation(
+    HardwareInformation* hardwareInformation) {
+  HardwareInformation hw;
+  hw.credentialStoreName =
+      "Identity Credential Cuttlefish Remote Implementation";
+  hw.credentialStoreAuthorName = "Google";
+  hw.dataChunkSize = kGcmChunkSize;
+  hw.isDirectAccess = false;
+  hw.supportedDocTypes = {};
+  *hardwareInformation = hw;
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredentialStore::createCredential(
+    const string& docType, bool testCredential,
+    shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
+  sp<SecureHardwareProvisioningProxy> hwProxy =
+      hwProxyFactory_->createProvisioningProxy();
+  shared_ptr<WritableIdentityCredential> wc =
+      ndk::SharedRefBase::make<WritableIdentityCredential>(hwProxy, docType,
+                                                           testCredential);
+  if (!wc->initialize()) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "Error initializing WritableIdentityCredential"));
+  }
+  *outWritableCredential = wc;
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredentialStore::getCredential(
+    CipherSuite cipherSuite, const vector<uint8_t>& credentialData,
+    shared_ptr<IIdentityCredential>* outCredential) {
+  // We only support CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 right
+  // now.
+  if (cipherSuite !=
+      CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_CIPHER_SUITE_NOT_SUPPORTED,
+        "Unsupported cipher suite"));
+  }
+
+  sp<SecureHardwarePresentationProxy> hwProxy =
+      hwProxyFactory_->createPresentationProxy();
+  shared_ptr<IdentityCredential> credential =
+      ndk::SharedRefBase::make<IdentityCredential>(hwProxyFactory_, hwProxy,
+                                                   credentialData);
+  auto ret = credential->initialize();
+  if (ret != IIdentityCredentialStore::STATUS_OK) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        int(ret), "Error initializing IdentityCredential"));
+  }
+  *outCredential = credential;
+  return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace aidl::android::hardware::identity
diff --git a/guest/hals/identity/common/IdentityCredentialStore.h b/guest/hals/identity/common/IdentityCredentialStore.h
new file mode 100644
index 0000000..1d65b2c
--- /dev/null
+++ b/guest/hals/identity/common/IdentityCredentialStore.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H
+#define ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H
+
+#include <aidl/android/hardware/identity/BnIdentityCredentialStore.h>
+#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
+
+#include "SecureHardwareProxy.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwareProxyFactory;
+using ::std::shared_ptr;
+using ::std::string;
+using ::std::vector;
+
+class IdentityCredentialStore : public BnIdentityCredentialStore {
+ public:
+  IdentityCredentialStore(sp<SecureHardwareProxyFactory> hwProxyFactory)
+      : hwProxyFactory_(hwProxyFactory) {}
+
+  // The GCM chunk size used by this implementation is 64 KiB.
+  static constexpr size_t kGcmChunkSize = 64 * 1024;
+
+  // Methods from IIdentityCredentialStore follow.
+  ndk::ScopedAStatus getHardwareInformation(
+      HardwareInformation* hardwareInformation) override;
+
+  ndk::ScopedAStatus createCredential(
+      const string& docType, bool testCredential,
+      shared_ptr<IWritableIdentityCredential>* outWritableCredential) override;
+
+  ndk::ScopedAStatus getCredential(
+      CipherSuite cipherSuite, const vector<uint8_t>& credentialData,
+      shared_ptr<IIdentityCredential>* outCredential) override;
+
+ private:
+  sp<SecureHardwareProxyFactory> hwProxyFactory_;
+};
+
+}  // namespace aidl::android::hardware::identity
+
+#endif  // ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H
diff --git a/guest/hals/identity/common/SecureHardwareProxy.h b/guest/hals/identity/common/SecureHardwareProxy.h
new file mode 100644
index 0000000..bd252a7
--- /dev/null
+++ b/guest/hals/identity/common/SecureHardwareProxy.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
+#define ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
+
+#include <utils/RefBase.h>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace android::hardware::identity {
+
+using ::android::RefBase;
+using ::std::optional;
+using ::std::pair;
+using ::std::string;
+using ::std::vector;
+
+// These classes are used to communicate with Secure Hardware. They mimic the
+// API in libEmbeddedIC 1:1 (except for using C++ types) as each call is
+// intended to be forwarded to the Secure Hardware.
+//
+// Instances are instantiated when a provisioning or presentation session
+// starts. When the session is complete, the shutdown() method is called.
+//
+
+// Forward declare.
+//
+class SecureHardwareProvisioningProxy;
+class SecureHardwarePresentationProxy;
+
+// This is a class used to create proxies.
+//
+class SecureHardwareProxyFactory : public RefBase {
+ public:
+  SecureHardwareProxyFactory() {}
+  virtual ~SecureHardwareProxyFactory() {}
+
+  virtual sp<SecureHardwareProvisioningProxy> createProvisioningProxy() = 0;
+  virtual sp<SecureHardwarePresentationProxy> createPresentationProxy() = 0;
+};
+
+// The proxy used for provisioning.
+//
+class SecureHardwareProvisioningProxy : public RefBase {
+ public:
+  SecureHardwareProvisioningProxy() {}
+  virtual ~SecureHardwareProvisioningProxy() {}
+
+  virtual bool initialize(bool testCredential) = 0;
+
+  virtual bool initializeForUpdate(bool testCredential, string docType,
+                                   vector<uint8_t> encryptedCredentialKeys) = 0;
+
+  // Returns public key certificate chain with attestation.
+  //
+  // This must return an entire certificate chain and its implementation must
+  // be coordinated with the implementation of eicOpsCreateCredentialKey() on
+  // the TA side (which may return just a single certificate or the entire
+  // chain).
+  virtual optional<vector<uint8_t>> createCredentialKey(
+      const vector<uint8_t>& challenge,
+      const vector<uint8_t>& applicationId) = 0;
+
+  virtual bool startPersonalization(int accessControlProfileCount,
+                                    vector<int> entryCounts,
+                                    const string& docType,
+                                    size_t expectedProofOfProvisioningSize) = 0;
+
+  // Returns MAC (28 bytes).
+  virtual optional<vector<uint8_t>> addAccessControlProfile(
+      int id, const vector<uint8_t>& readerCertificate,
+      bool userAuthenticationRequired, uint64_t timeoutMillis,
+      uint64_t secureUserId) = 0;
+
+  virtual bool beginAddEntry(const vector<int>& accessControlProfileIds,
+                             const string& nameSpace, const string& name,
+                             uint64_t entrySize) = 0;
+
+  // Returns encryptedContent.
+  virtual optional<vector<uint8_t>> addEntryValue(
+      const vector<int>& accessControlProfileIds, const string& nameSpace,
+      const string& name, const vector<uint8_t>& content) = 0;
+
+  // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+  virtual optional<vector<uint8_t>> finishAddingEntries() = 0;
+
+  // Returns encryptedCredentialKeys (80 bytes).
+  virtual optional<vector<uint8_t>> finishGetCredentialData(
+      const string& docType) = 0;
+
+  virtual bool shutdown() = 0;
+};
+
+enum AccessCheckResult {
+  kOk,
+  kFailed,
+  kNoAccessControlProfiles,
+  kUserAuthenticationFailed,
+  kReaderAuthenticationFailed,
+};
+
+// The proxy used for presentation.
+//
+class SecureHardwarePresentationProxy : public RefBase {
+ public:
+  SecureHardwarePresentationProxy() {}
+  virtual ~SecureHardwarePresentationProxy() {}
+
+  virtual bool initialize(bool testCredential, string docType,
+                          vector<uint8_t> encryptedCredentialKeys) = 0;
+
+  // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+  virtual optional<pair<vector<uint8_t>, vector<uint8_t>>>
+  generateSigningKeyPair(string docType, time_t now) = 0;
+
+  // Returns private key
+  virtual optional<vector<uint8_t>> createEphemeralKeyPair() = 0;
+
+  virtual optional<uint64_t> createAuthChallenge() = 0;
+
+  virtual bool startRetrieveEntries() = 0;
+
+  virtual bool setAuthToken(uint64_t challenge, uint64_t secureUserId,
+                            uint64_t authenticatorId,
+                            int hardwareAuthenticatorType, uint64_t timeStamp,
+                            const vector<uint8_t>& mac,
+                            uint64_t verificationTokenChallenge,
+                            uint64_t verificationTokenTimestamp,
+                            int verificationTokenSecurityLevel,
+                            const vector<uint8_t>& verificationTokenMac) = 0;
+
+  virtual bool pushReaderCert(const vector<uint8_t>& certX509) = 0;
+
+  virtual optional<bool> validateAccessControlProfile(
+      int id, const vector<uint8_t>& readerCertificate,
+      bool userAuthenticationRequired, int timeoutMillis, uint64_t secureUserId,
+      const vector<uint8_t>& mac) = 0;
+
+  virtual bool validateRequestMessage(
+      const vector<uint8_t>& sessionTranscript,
+      const vector<uint8_t>& requestMessage, int coseSignAlg,
+      const vector<uint8_t>& readerSignatureOfToBeSigned) = 0;
+
+  virtual bool calcMacKey(const vector<uint8_t>& sessionTranscript,
+                          const vector<uint8_t>& readerEphemeralPublicKey,
+                          const vector<uint8_t>& signingKeyBlob,
+                          const string& docType,
+                          unsigned int numNamespacesWithValues,
+                          size_t expectedProofOfProvisioningSize) = 0;
+
+  virtual AccessCheckResult startRetrieveEntryValue(
+      const string& nameSpace, const string& name,
+      unsigned int newNamespaceNumEntries, int32_t entrySize,
+      const vector<int32_t>& accessControlProfileIds) = 0;
+
+  virtual optional<vector<uint8_t>> retrieveEntryValue(
+      const vector<uint8_t>& encryptedContent, const string& nameSpace,
+      const string& name, const vector<int32_t>& accessControlProfileIds) = 0;
+
+  virtual optional<vector<uint8_t>> finishRetrieval();
+
+  virtual optional<vector<uint8_t>> deleteCredential(
+      const string& docType, const vector<uint8_t>& challenge,
+      bool includeChallenge, size_t proofOfDeletionCborSize) = 0;
+
+  virtual optional<vector<uint8_t>> proveOwnership(
+      const string& docType, bool testCredential,
+      const vector<uint8_t>& challenge, size_t proofOfOwnershipCborSize) = 0;
+
+  virtual bool shutdown() = 0;
+};
+
+}  // namespace android::hardware::identity
+
+#endif  // ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
diff --git a/guest/hals/identity/common/WritableIdentityCredential.cpp b/guest/hals/identity/common/WritableIdentityCredential.cpp
new file mode 100644
index 0000000..13ee244
--- /dev/null
+++ b/guest/hals/identity/common/WritableIdentityCredential.cpp
@@ -0,0 +1,424 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "WritableIdentityCredential"
+
+#include "WritableIdentityCredential.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include <utility>
+
+#include "IdentityCredentialStore.h"
+
+#include "SecureHardwareProxy.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::android::base::StringPrintf;
+using ::std::optional;
+using namespace ::android::hardware::identity;
+
+bool WritableIdentityCredential::initialize() {
+  if (!hwProxy_->initialize(testCredential_)) {
+    LOG(ERROR) << "hwProxy->initialize() failed";
+    return false;
+  }
+  startPersonalizationCalled_ = false;
+  firstEntry_ = true;
+
+  return true;
+}
+
+// Used when updating a credential. Returns false on failure.
+bool WritableIdentityCredential::initializeForUpdate(
+    const vector<uint8_t>& encryptedCredentialKeys) {
+  if (!hwProxy_->initializeForUpdate(testCredential_, docType_,
+                                     encryptedCredentialKeys)) {
+    LOG(ERROR) << "hwProxy->initializeForUpdate() failed";
+    return false;
+  }
+  startPersonalizationCalled_ = false;
+  firstEntry_ = true;
+
+  return true;
+}
+
+WritableIdentityCredential::~WritableIdentityCredential() {}
+
+ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate(
+    const vector<uint8_t>& attestationApplicationId,
+    const vector<uint8_t>& attestationChallenge,
+    vector<Certificate>* outCertificateChain) {
+  if (getAttestationCertificateAlreadyCalled_) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "Error attestation certificate previously generated"));
+  }
+  getAttestationCertificateAlreadyCalled_ = true;
+
+  if (attestationChallenge.empty()) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "Challenge can not be empty"));
+  }
+
+  optional<vector<uint8_t>> certChain = hwProxy_->createCredentialKey(
+      attestationChallenge, attestationApplicationId);
+  if (!certChain) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "Error generating attestation certificate chain"));
+  }
+
+  optional<vector<vector<uint8_t>>> certs =
+      support::certificateChainSplit(certChain.value());
+  if (!certs) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "Error splitting chain into separate certificates"));
+  }
+
+  *outCertificateChain = vector<Certificate>();
+  for (const vector<uint8_t>& cert : certs.value()) {
+    Certificate c = Certificate();
+    c.encodedCertificate = cert;
+    outCertificateChain->push_back(std::move(c));
+  }
+
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus
+WritableIdentityCredential::setExpectedProofOfProvisioningSize(
+    int32_t expectedProofOfProvisioningSize) {
+  expectedProofOfProvisioningSize_ = expectedProofOfProvisioningSize;
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::startPersonalization(
+    int32_t accessControlProfileCount, const vector<int32_t>& entryCounts) {
+  if (startPersonalizationCalled_) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "startPersonalization called already"));
+  }
+  startPersonalizationCalled_ = true;
+
+  numAccessControlProfileRemaining_ = accessControlProfileCount;
+  remainingEntryCounts_ = entryCounts;
+  entryNameSpace_ = "";
+
+  signedDataAccessControlProfiles_ = cppbor::Array();
+  signedDataNamespaces_ = cppbor::Map();
+  signedDataCurrentNamespace_ = cppbor::Array();
+
+  if (!hwProxy_->startPersonalization(accessControlProfileCount, entryCounts,
+                                      docType_,
+                                      expectedProofOfProvisioningSize_)) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED, "eicStartPersonalization"));
+  }
+
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile(
+    int32_t id, const Certificate& readerCertificate,
+    bool userAuthenticationRequired, int64_t timeoutMillis,
+    int64_t secureUserId,
+    SecureAccessControlProfile* outSecureAccessControlProfile) {
+  if (numAccessControlProfileRemaining_ == 0) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "numAccessControlProfileRemaining_ is 0 and expected non-zero"));
+  }
+
+  if (accessControlProfileIds_.find(id) != accessControlProfileIds_.end()) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "Access Control Profile id must be unique"));
+  }
+  accessControlProfileIds_.insert(id);
+
+  if (id < 0 || id >= 32) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "Access Control Profile id must be non-negative and less than 32"));
+  }
+
+  // Spec requires if |userAuthenticationRequired| is false, then
+  // |timeoutMillis| must also be zero.
+  if (!userAuthenticationRequired && timeoutMillis != 0) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "userAuthenticationRequired is false but timeout is non-zero"));
+  }
+
+  optional<vector<uint8_t>> mac = hwProxy_->addAccessControlProfile(
+      id, readerCertificate.encodedCertificate, userAuthenticationRequired,
+      timeoutMillis, secureUserId);
+  if (!mac) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED, "eicAddAccessControlProfile"));
+  }
+
+  SecureAccessControlProfile profile;
+  profile.id = id;
+  profile.readerCertificate = readerCertificate;
+  profile.userAuthenticationRequired = userAuthenticationRequired;
+  profile.timeoutMillis = timeoutMillis;
+  profile.secureUserId = secureUserId;
+  profile.mac = mac.value();
+  cppbor::Map profileMap;
+  profileMap.add("id", profile.id);
+  if (profile.readerCertificate.encodedCertificate.size() > 0) {
+    profileMap.add("readerCertificate",
+                   cppbor::Bstr(profile.readerCertificate.encodedCertificate));
+  }
+  if (profile.userAuthenticationRequired) {
+    profileMap.add("userAuthenticationRequired",
+                   profile.userAuthenticationRequired);
+    profileMap.add("timeoutMillis", profile.timeoutMillis);
+  }
+  signedDataAccessControlProfiles_.add(std::move(profileMap));
+
+  numAccessControlProfileRemaining_--;
+
+  *outSecureAccessControlProfile = profile;
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::beginAddEntry(
+    const vector<int32_t>& accessControlProfileIds, const string& nameSpace,
+    const string& name, int32_t entrySize) {
+  if (numAccessControlProfileRemaining_ != 0) {
+    LOG(ERROR) << "numAccessControlProfileRemaining_ is "
+               << numAccessControlProfileRemaining_ << " and expected zero";
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "numAccessControlProfileRemaining_ is not zero"));
+  }
+
+  // Ensure passed-in profile ids reference valid access control profiles
+  for (const int32_t id : accessControlProfileIds) {
+    if (accessControlProfileIds_.find(id) == accessControlProfileIds_.end()) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "An id in accessControlProfileIds references non-existing ACP"));
+    }
+  }
+
+  if (remainingEntryCounts_.size() == 0) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "No more namespaces to add to"));
+  }
+
+  // Handle initial beginEntry() call.
+  if (firstEntry_) {
+    firstEntry_ = false;
+    entryNameSpace_ = nameSpace;
+    allNameSpaces_.insert(nameSpace);
+  }
+
+  // If the namespace changed...
+  if (nameSpace != entryNameSpace_) {
+    if (allNameSpaces_.find(nameSpace) != allNameSpaces_.end()) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "Name space cannot be added in interleaving fashion"));
+    }
+
+    // Then check that all entries in the previous namespace have been added..
+    if (remainingEntryCounts_[0] != 0) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "New namespace but a non-zero number of entries remain to be added"));
+    }
+    remainingEntryCounts_.erase(remainingEntryCounts_.begin());
+    remainingEntryCounts_[0] -= 1;
+    allNameSpaces_.insert(nameSpace);
+
+    if (signedDataCurrentNamespace_.size() > 0) {
+      signedDataNamespaces_.add(entryNameSpace_,
+                                std::move(signedDataCurrentNamespace_));
+      signedDataCurrentNamespace_ = cppbor::Array();
+    }
+  } else {
+    // Same namespace...
+    if (remainingEntryCounts_[0] == 0) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "Same namespace but no entries remain to be added"));
+    }
+    remainingEntryCounts_[0] -= 1;
+  }
+
+  entryRemainingBytes_ = entrySize;
+  entryNameSpace_ = nameSpace;
+  entryName_ = name;
+  entryAccessControlProfileIds_ = accessControlProfileIds;
+  entryBytes_.resize(0);
+  // LOG(INFO) << "name=" << name << " entrySize=" << entrySize;
+
+  if (!hwProxy_->beginAddEntry(accessControlProfileIds, nameSpace, name,
+                               entrySize)) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED, "eicBeginAddEntry"));
+  }
+
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::addEntryValue(
+    const vector<uint8_t>& content, vector<uint8_t>* outEncryptedContent) {
+  size_t contentSize = content.size();
+
+  if (contentSize > IdentityCredentialStore::kGcmChunkSize) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "Passed in chunk of is bigger than kGcmChunkSize"));
+  }
+  if (contentSize > entryRemainingBytes_) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "Passed in chunk is bigger than remaining space"));
+  }
+
+  entryBytes_.insert(entryBytes_.end(), content.begin(), content.end());
+  entryRemainingBytes_ -= contentSize;
+  if (entryRemainingBytes_ > 0) {
+    if (contentSize != IdentityCredentialStore::kGcmChunkSize) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "Retrieved non-final chunk which isn't kGcmChunkSize"));
+    }
+  }
+
+  optional<vector<uint8_t>> encryptedContent = hwProxy_->addEntryValue(
+      entryAccessControlProfileIds_, entryNameSpace_, entryName_, content);
+  if (!encryptedContent) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED, "eicAddEntryValue"));
+  }
+
+  if (entryRemainingBytes_ == 0) {
+    // TODO: ideally do do this without parsing the data (but still validate
+    // data is valid CBOR).
+    auto [item, _, message] = cppbor::parse(entryBytes_);
+    if (item == nullptr) {
+      return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+          IIdentityCredentialStore::STATUS_INVALID_DATA,
+          "Data is not valid CBOR"));
+    }
+    cppbor::Map entryMap;
+    entryMap.add("name", entryName_);
+    entryMap.add("value", std::move(item));
+    cppbor::Array profileIdArray;
+    for (auto id : entryAccessControlProfileIds_) {
+      profileIdArray.add(id);
+    }
+    entryMap.add("accessControlProfiles", std::move(profileIdArray));
+    signedDataCurrentNamespace_.add(std::move(entryMap));
+  }
+
+  *outEncryptedContent = encryptedContent.value();
+  return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries(
+    vector<uint8_t>* outCredentialData,
+    vector<uint8_t>* outProofOfProvisioningSignature) {
+  if (numAccessControlProfileRemaining_ != 0) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "numAccessControlProfileRemaining_ is not 0 and expected zero"));
+  }
+
+  if (remainingEntryCounts_.size() > 1 || remainingEntryCounts_[0] != 0) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        "More entry spaces remain than startPersonalization configured"));
+  }
+
+  if (signedDataCurrentNamespace_.size() > 0) {
+    signedDataNamespaces_.add(entryNameSpace_,
+                              std::move(signedDataCurrentNamespace_));
+  }
+  cppbor::Array popArray;
+  popArray.add("ProofOfProvisioning")
+      .add(docType_)
+      .add(std::move(signedDataAccessControlProfiles_))
+      .add(std::move(signedDataNamespaces_))
+      .add(testCredential_);
+  vector<uint8_t> encodedCbor = popArray.encode();
+
+  if (encodedCbor.size() != expectedProofOfProvisioningSize_) {
+    LOG(ERROR) << "CBOR for proofOfProvisioning is " << encodedCbor.size()
+               << " bytes, "
+               << "was expecting " << expectedProofOfProvisioningSize_;
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_INVALID_DATA,
+        StringPrintf("Unexpected CBOR size %zd for proofOfProvisioning, was "
+                     "expecting %zd",
+                     encodedCbor.size(), expectedProofOfProvisioningSize_)
+            .c_str()));
+  }
+
+  optional<vector<uint8_t>> signatureOfToBeSigned =
+      hwProxy_->finishAddingEntries();
+  if (!signatureOfToBeSigned) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED, "eicFinishAddingEntries"));
+  }
+
+  optional<vector<uint8_t>> signature =
+      support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+                                          encodedCbor,  // data
+                                          {});          // certificateChain
+  if (!signature) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
+  }
+
+  optional<vector<uint8_t>> encryptedCredentialKeys =
+      hwProxy_->finishGetCredentialData(docType_);
+  if (!encryptedCredentialKeys) {
+    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+        IIdentityCredentialStore::STATUS_FAILED,
+        "Error generating encrypted CredentialKeys"));
+  }
+  cppbor::Array array;
+  array.add(docType_);
+  array.add(testCredential_);
+  array.add(encryptedCredentialKeys.value());
+  vector<uint8_t> credentialData = array.encode();
+
+  *outCredentialData = credentialData;
+  *outProofOfProvisioningSignature = signature.value();
+  hwProxy_->shutdown();
+
+  return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace aidl::android::hardware::identity
diff --git a/guest/hals/identity/common/WritableIdentityCredential.h b/guest/hals/identity/common/WritableIdentityCredential.h
new file mode 100644
index 0000000..47a22ad
--- /dev/null
+++ b/guest/hals/identity/common/WritableIdentityCredential.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H
+#define ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H
+
+#include <aidl/android/hardware/identity/BnWritableIdentityCredential.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <cppbor.h>
+#include <set>
+
+#include "IdentityCredentialStore.h"
+#include "SecureHardwareProxy.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwareProvisioningProxy;
+using ::std::set;
+using ::std::string;
+using ::std::vector;
+
+class WritableIdentityCredential : public BnWritableIdentityCredential {
+ public:
+  // For a new credential, call initialize() right after construction.
+  //
+  // For an updated credential, call initializeForUpdate() right after
+  // construction.
+  //
+  WritableIdentityCredential(sp<SecureHardwareProvisioningProxy> hwProxy,
+                             const string& docType, bool testCredential)
+      : hwProxy_(hwProxy), docType_(docType), testCredential_(testCredential) {}
+
+  ~WritableIdentityCredential();
+
+  // Creates the Credential Key. Returns false on failure.
+  bool initialize();
+
+  // Used when updating a credential. Returns false on failure.
+  bool initializeForUpdate(const vector<uint8_t>& encryptedCredentialKeys);
+
+  // Methods from IWritableIdentityCredential follow.
+  ndk::ScopedAStatus getAttestationCertificate(
+      const vector<uint8_t>& attestationApplicationId,
+      const vector<uint8_t>& attestationChallenge,
+      vector<Certificate>* outCertificateChain) override;
+
+  ndk::ScopedAStatus setExpectedProofOfProvisioningSize(
+      int32_t expectedProofOfProvisioningSize) override;
+
+  ndk::ScopedAStatus startPersonalization(
+      int32_t accessControlProfileCount,
+      const vector<int32_t>& entryCounts) override;
+
+  ndk::ScopedAStatus addAccessControlProfile(
+      int32_t id, const Certificate& readerCertificate,
+      bool userAuthenticationRequired, int64_t timeoutMillis,
+      int64_t secureUserId,
+      SecureAccessControlProfile* outSecureAccessControlProfile) override;
+
+  ndk::ScopedAStatus beginAddEntry(
+      const vector<int32_t>& accessControlProfileIds, const string& nameSpace,
+      const string& name, int32_t entrySize) override;
+  ndk::ScopedAStatus addEntryValue(
+      const vector<uint8_t>& content,
+      vector<uint8_t>* outEncryptedContent) override;
+
+  ndk::ScopedAStatus finishAddingEntries(
+      vector<uint8_t>* outCredentialData,
+      vector<uint8_t>* outProofOfProvisioningSignature) override;
+
+ private:
+  // Set by constructor.
+  sp<SecureHardwareProvisioningProxy> hwProxy_;
+  string docType_;
+  bool testCredential_;
+
+  // This is set in initialize().
+  bool startPersonalizationCalled_;
+  bool firstEntry_;
+
+  // This is set in getAttestationCertificate().
+  bool getAttestationCertificateAlreadyCalled_ = false;
+
+  // These fields are initialized during startPersonalization()
+  size_t numAccessControlProfileRemaining_;
+  vector<int32_t> remainingEntryCounts_;
+  cppbor::Array signedDataAccessControlProfiles_;
+  cppbor::Map signedDataNamespaces_;
+  cppbor::Array signedDataCurrentNamespace_;
+  size_t expectedProofOfProvisioningSize_;
+
+  // This field is initialized in addAccessControlProfile
+  set<int32_t> accessControlProfileIds_;
+
+  // These fields are initialized during beginAddEntry()
+  size_t entryRemainingBytes_;
+  string entryNameSpace_;
+  string entryName_;
+  vector<int32_t> entryAccessControlProfileIds_;
+  vector<uint8_t> entryBytes_;
+  set<string> allNameSpaces_;
+};
+
+}  // namespace aidl::android::hardware::identity
+
+#endif  // ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H
diff --git a/guest/hals/identity/libeic/EicCbor.c b/guest/hals/identity/libeic/EicCbor.c
new file mode 100644
index 0000000..b496342
--- /dev/null
+++ b/guest/hals/identity/libeic/EicCbor.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EicCbor.h"
+
+void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize) {
+  eicMemSet(cbor, '\0', sizeof(EicCbor));
+  cbor->size = 0;
+  cbor->bufferSize = bufferSize;
+  cbor->buffer = buffer;
+  cbor->digestType = EIC_CBOR_DIGEST_TYPE_SHA256;
+  eicOpsSha256Init(&cbor->digester.sha256);
+}
+
+void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
+                           const uint8_t* hmacKey, size_t hmacKeySize) {
+  eicMemSet(cbor, '\0', sizeof(EicCbor));
+  cbor->size = 0;
+  cbor->bufferSize = bufferSize;
+  cbor->buffer = buffer;
+  cbor->digestType = EIC_CBOR_DIGEST_TYPE_HMAC_SHA256;
+  eicOpsHmacSha256Init(&cbor->digester.hmacSha256, hmacKey, hmacKeySize);
+}
+
+void eicCborEnableSecondaryDigesterSha256(EicCbor* cbor, EicSha256Ctx* sha256) {
+  cbor->secondaryDigesterSha256 = sha256;
+}
+
+void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+  switch (cbor->digestType) {
+    case EIC_CBOR_DIGEST_TYPE_SHA256:
+      eicOpsSha256Final(&cbor->digester.sha256, digest);
+      break;
+    case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256:
+      eicOpsHmacSha256Final(&cbor->digester.hmacSha256, digest);
+      break;
+  }
+}
+
+void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size) {
+  switch (cbor->digestType) {
+    case EIC_CBOR_DIGEST_TYPE_SHA256:
+      eicOpsSha256Update(&cbor->digester.sha256, data, size);
+      break;
+    case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256:
+      eicOpsHmacSha256Update(&cbor->digester.hmacSha256, data, size);
+      break;
+  }
+  if (cbor->secondaryDigesterSha256 != NULL) {
+    eicOpsSha256Update(cbor->secondaryDigesterSha256, data, size);
+  }
+
+  if (cbor->size >= cbor->bufferSize) {
+    cbor->size += size;
+    return;
+  }
+
+  size_t numBytesLeft = cbor->bufferSize - cbor->size;
+  size_t numBytesToCopy = size;
+  if (numBytesToCopy > numBytesLeft) {
+    numBytesToCopy = numBytesLeft;
+  }
+  eicMemCpy(cbor->buffer + cbor->size, data, numBytesToCopy);
+
+  cbor->size += size;
+}
+
+size_t eicCborAdditionalLengthBytesFor(size_t size) {
+  if (size < 24) {
+    return 0;
+  } else if (size <= 0xff) {
+    return 1;
+  } else if (size <= 0xffff) {
+    return 2;
+  } else if (size <= 0xffffffff) {
+    return 4;
+  }
+  return 8;
+}
+
+void eicCborBegin(EicCbor* cbor, int majorType, uint64_t size) {
+  uint8_t data[9];
+
+  if (size < 24) {
+    data[0] = (majorType << 5) | size;
+    eicCborAppend(cbor, data, 1);
+  } else if (size <= 0xff) {
+    data[0] = (majorType << 5) | 24;
+    data[1] = size;
+    eicCborAppend(cbor, data, 2);
+  } else if (size <= 0xffff) {
+    data[0] = (majorType << 5) | 25;
+    data[1] = size >> 8;
+    data[2] = size & 0xff;
+    eicCborAppend(cbor, data, 3);
+  } else if (size <= 0xffffffff) {
+    data[0] = (majorType << 5) | 26;
+    data[1] = (size >> 24) & 0xff;
+    data[2] = (size >> 16) & 0xff;
+    data[3] = (size >> 8) & 0xff;
+    data[4] = size & 0xff;
+    eicCborAppend(cbor, data, 5);
+  } else {
+    data[0] = (majorType << 5) | 27;
+    data[1] = (((uint64_t)size) >> 56) & 0xff;
+    data[2] = (((uint64_t)size) >> 48) & 0xff;
+    data[3] = (((uint64_t)size) >> 40) & 0xff;
+    data[4] = (((uint64_t)size) >> 32) & 0xff;
+    data[5] = (((uint64_t)size) >> 24) & 0xff;
+    data[6] = (((uint64_t)size) >> 16) & 0xff;
+    data[7] = (((uint64_t)size) >> 8) & 0xff;
+    data[8] = ((uint64_t)size) & 0xff;
+    eicCborAppend(cbor, data, 9);
+  }
+}
+
+void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data,
+                             size_t dataSize) {
+  eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dataSize);
+  eicCborAppend(cbor, data, dataSize);
+}
+
+void eicCborAppendString(EicCbor* cbor, const char* str, size_t strLength) {
+  eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_STRING, strLength);
+  eicCborAppend(cbor, (const uint8_t*)str, strLength);
+}
+
+void eicCborAppendStringZ(EicCbor* cbor, const char* str) {
+  eicCborAppendString(cbor, str, eicStrLen(str));
+}
+
+void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue) {
+  eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SIMPLE, simpleValue);
+}
+
+void eicCborAppendBool(EicCbor* cbor, bool value) {
+  uint8_t simpleValue =
+      value ? EIC_CBOR_SIMPLE_VALUE_TRUE : EIC_CBOR_SIMPLE_VALUE_FALSE;
+  eicCborAppendSimple(cbor, simpleValue);
+}
+
+void eicCborAppendSemantic(EicCbor* cbor, uint64_t value) {
+  size_t encoded = value;
+  eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SEMANTIC, encoded);
+}
+
+void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value) {
+  uint64_t encoded = value;
+  eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_UNSIGNED, encoded);
+}
+
+void eicCborAppendNumber(EicCbor* cbor, int64_t value) {
+  if (value < 0) {
+    uint64_t encoded = -1 - value;
+    eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_NEGATIVE, encoded);
+  } else {
+    eicCborAppendUnsigned(cbor, value);
+  }
+}
+
+void eicCborAppendArray(EicCbor* cbor, size_t numElements) {
+  eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, numElements);
+}
+
+void eicCborAppendMap(EicCbor* cbor, size_t numPairs) {
+  eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_MAP, numPairs);
+}
+
+bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id,
+                              const uint8_t* readerCertificate,
+                              size_t readerCertificateSize,
+                              bool userAuthenticationRequired,
+                              uint64_t timeoutMillis, uint64_t secureUserId) {
+  size_t numPairs = 1;
+  if (readerCertificateSize > 0) {
+    numPairs += 1;
+  }
+  if (userAuthenticationRequired) {
+    numPairs += 2;
+    if (secureUserId > 0) {
+      numPairs += 1;
+    }
+  }
+  eicCborAppendMap(cborBuilder, numPairs);
+  eicCborAppendStringZ(cborBuilder, "id");
+  eicCborAppendUnsigned(cborBuilder, id);
+  if (readerCertificateSize > 0) {
+    eicCborAppendStringZ(cborBuilder, "readerCertificate");
+    eicCborAppendByteString(cborBuilder, readerCertificate,
+                            readerCertificateSize);
+  }
+  if (userAuthenticationRequired) {
+    eicCborAppendStringZ(cborBuilder, "userAuthenticationRequired");
+    eicCborAppendBool(cborBuilder, userAuthenticationRequired);
+    eicCborAppendStringZ(cborBuilder, "timeoutMillis");
+    eicCborAppendUnsigned(cborBuilder, timeoutMillis);
+    if (secureUserId > 0) {
+      eicCborAppendStringZ(cborBuilder, "secureUserId");
+      eicCborAppendUnsigned(cborBuilder, secureUserId);
+    }
+  }
+
+  if (cborBuilder->size > cborBuilder->bufferSize) {
+    eicDebug("Buffer for ACP CBOR is too small (%zd) - need %zd bytes",
+             cborBuilder->bufferSize, cborBuilder->size);
+    return false;
+  }
+
+  return true;
+}
+
+bool eicCborCalcEntryAdditionalData(
+    const uint8_t* accessControlProfileIds, size_t numAccessControlProfileIds,
+    const char* nameSpace, size_t nameSpaceLength, const char* name,
+    size_t nameLength, uint8_t* cborBuffer, size_t cborBufferSize,
+    size_t* outAdditionalDataCborSize,
+    uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]) {
+  EicCbor cborBuilder;
+
+  eicCborInit(&cborBuilder, cborBuffer, cborBufferSize);
+  eicCborAppendMap(&cborBuilder, 3);
+  eicCborAppendStringZ(&cborBuilder, "Namespace");
+  eicCborAppendString(&cborBuilder, nameSpace, nameSpaceLength);
+  eicCborAppendStringZ(&cborBuilder, "Name");
+  eicCborAppendString(&cborBuilder, name, nameLength);
+  eicCborAppendStringZ(&cborBuilder, "AccessControlProfileIds");
+  eicCborAppendArray(&cborBuilder, numAccessControlProfileIds);
+  for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+    eicCborAppendNumber(&cborBuilder, accessControlProfileIds[n]);
+  }
+  if (cborBuilder.size > cborBufferSize) {
+    eicDebug(
+        "Not enough space for additionalData - buffer is only %zd bytes, "
+        "content is %zd",
+        cborBufferSize, cborBuilder.size);
+    return false;
+  }
+  if (outAdditionalDataCborSize != NULL) {
+    *outAdditionalDataCborSize = cborBuilder.size;
+  }
+  eicCborFinal(&cborBuilder, additionalDataSha256);
+  return true;
+}
diff --git a/guest/hals/identity/libeic/EicCbor.h b/guest/hals/identity/libeic/EicCbor.h
new file mode 100644
index 0000000..384766f
--- /dev/null
+++ b/guest/hals/identity/libeic/EicCbor.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicOps.h"
+
+typedef enum {
+  EIC_CBOR_DIGEST_TYPE_SHA256,
+  EIC_CBOR_DIGEST_TYPE_HMAC_SHA256,
+} EicCborDigestType;
+
+/* EicCbor is a utility class to build CBOR data structures and calculate
+ * digests on the fly.
+ */
+typedef struct {
+  // Contains the size of the built CBOR, even if it exceeds bufferSize (will
+  // never write to buffer beyond bufferSize though)
+  size_t size;
+
+  // The size of the buffer. Is zero if no data is recorded in which case
+  // only digesting is performed.
+  size_t bufferSize;
+
+  // Whether we're producing a SHA-256 or HMAC-SHA256 digest.
+  EicCborDigestType digestType;
+
+  // The SHA-256 digester object.
+  union {
+    EicSha256Ctx sha256;
+    EicHmacSha256Ctx hmacSha256;
+  } digester;
+
+  // The secondary digester, may be unset.
+  EicSha256Ctx* secondaryDigesterSha256;
+
+  // The buffer used for building up CBOR or NULL if bufferSize is 0.
+  uint8_t* buffer;
+} EicCbor;
+
+/* Initializes an EicCbor.
+ *
+ * The given buffer will be used, up to bufferSize.
+ *
+ * If bufferSize is 0, buffer may be NULL.
+ */
+void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize);
+
+/* Like eicCborInit() but uses HMAC-SHA256 instead of SHA-256.
+ */
+void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
+                           const uint8_t* hmacKey, size_t hmacKeySize);
+
+/* Enables a secondary digester.
+ *
+ * May be enabled midway through processing, this can be used to e.g. calculate
+ * a digest of Sig_structure (for COSE_Sign1) and a separate digest of its
+ * payload.
+ */
+void eicCborEnableSecondaryDigesterSha256(EicCbor* cbor, EicSha256Ctx* sha256);
+
+/* Finishes building CBOR and returns the digest. */
+void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+/* Appends CBOR data to the EicCbor. */
+void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size);
+
+#define EIC_CBOR_MAJOR_TYPE_UNSIGNED 0
+#define EIC_CBOR_MAJOR_TYPE_NEGATIVE 1
+#define EIC_CBOR_MAJOR_TYPE_BYTE_STRING 2
+#define EIC_CBOR_MAJOR_TYPE_STRING 3
+#define EIC_CBOR_MAJOR_TYPE_ARRAY 4
+#define EIC_CBOR_MAJOR_TYPE_MAP 5
+#define EIC_CBOR_MAJOR_TYPE_SEMANTIC 6
+#define EIC_CBOR_MAJOR_TYPE_SIMPLE 7
+
+#define EIC_CBOR_SIMPLE_VALUE_FALSE 20
+#define EIC_CBOR_SIMPLE_VALUE_TRUE 21
+
+#define EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR 24
+
+/* Begins a new CBOR value. */
+void eicCborBegin(EicCbor* cbor, int majorType, uint64_t size);
+
+/* Appends a bytestring. */
+void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data,
+                             size_t dataSize);
+
+/* Appends a UTF-8 string. */
+void eicCborAppendString(EicCbor* cbor, const char* str, size_t strLength);
+
+/* Appends a NUL-terminated UTF-8 string. */
+void eicCborAppendStringZ(EicCbor* cbor, const char* str);
+
+/* Appends a simple value. */
+void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue);
+
+/* Appends a boolean. */
+void eicCborAppendBool(EicCbor* cbor, bool value);
+
+/* Appends a semantic */
+void eicCborAppendSemantic(EicCbor* cbor, uint64_t value);
+
+/* Appends an unsigned number. */
+void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value);
+
+/* Appends a number. */
+void eicCborAppendNumber(EicCbor* cbor, int64_t value);
+
+/* Starts appending an array.
+ *
+ * After this numElements CBOR elements must follow.
+ */
+void eicCborAppendArray(EicCbor* cbor, size_t numElements);
+
+/* Starts appending a map.
+ *
+ * After this numPairs pairs of CBOR elements must follow.
+ */
+void eicCborAppendMap(EicCbor* cbor, size_t numPairs);
+
+/* Calculates how many bytes are needed to store a size. */
+size_t eicCborAdditionalLengthBytesFor(size_t size);
+
+bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id,
+                              const uint8_t* readerCertificate,
+                              size_t readerCertificateSize,
+                              bool userAuthenticationRequired,
+                              uint64_t timeoutMillis, uint64_t secureUserId);
+
+bool eicCborCalcEntryAdditionalData(
+    const uint8_t* accessControlProfileIds, size_t numAccessControlProfileIds,
+    const char* nameSpace, size_t nameSpaceLength, const char* name,
+    size_t nameLength, uint8_t* cborBuffer, size_t cborBufferSize,
+    size_t* outAdditionalDataCborSize,
+    uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
diff --git a/guest/hals/identity/libeic/EicCommon.h b/guest/hals/identity/libeic/EicCommon.h
new file mode 100644
index 0000000..1fab26a
--- /dev/null
+++ b/guest/hals/identity/libeic/EicCommon.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_COMMON_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_COMMON_H
+
+// Feature version 202009:
+//
+//         CredentialKeys = [
+//              bstr,   ; storageKey, a 128-bit AES key
+//              bstr,   ; credentialPrivKey, the private key for credentialKey
+//         ]
+//
+// Feature version 202101:
+//
+//         CredentialKeys = [
+//              bstr,   ; storageKey, a 128-bit AES key
+//              bstr,   ; credentialPrivKey, the private key for credentialKey
+//              bstr    ; proofOfProvisioning SHA-256
+//         ]
+//
+// where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and
+// proofOfProvisioning SHA-256 is 32 bytes.
+#define EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202009 52
+#define EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101 86
+
+#endif  // ANDROID_HARDWARE_IDENTITY_EIC_COMMON_H
diff --git a/guest/hals/identity/libeic/EicOps.h b/guest/hals/identity/libeic/EicOps.h
new file mode 100644
index 0000000..849d6fc
--- /dev/null
+++ b/guest/hals/identity/libeic/EicOps.h
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+// Uncomment or define if debug messages are needed.
+//
+//#define EIC_DEBUG
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// The following defines must be set to something appropriate
+//
+//   EIC_SHA256_CONTEXT_SIZE - the size of EicSha256Ctx
+//   EIC_HMAC_SHA256_CONTEXT_SIZE - the size of EicHmacSha256Ctx
+//
+// For example, if EicSha256Ctx is implemented using BoringSSL this would be
+// defined as sizeof(SHA256_CTX).
+//
+// We expect the implementation to provide a header file with the name
+// EicOpsImpl.h to do all this.
+//
+#include "EicOpsImpl.h"
+
+#define EIC_SHA256_DIGEST_SIZE 32
+
+// The size of a P-256 private key.
+//
+#define EIC_P256_PRIV_KEY_SIZE 32
+
+// The size of a P-256 public key in uncompressed form.
+//
+// The public key is stored in uncompressed form, first the X coordinate, then
+// the Y coordinate.
+//
+#define EIC_P256_PUB_KEY_SIZE 64
+
+// Size of one of the coordinates in a curve-point.
+//
+#define EIC_P256_COORDINATE_SIZE 32
+
+// The size of an ECSDA signature using P-256.
+//
+// The R and S values are stored here, first R then S.
+//
+#define EIC_ECDSA_P256_SIGNATURE_SIZE 64
+
+#define EIC_AES_128_KEY_SIZE 16
+
+// The following are definitions of implementation functions the
+// underlying platform must provide.
+//
+
+struct EicSha256Ctx {
+  uint8_t reserved[EIC_SHA256_CONTEXT_SIZE];
+};
+typedef struct EicSha256Ctx EicSha256Ctx;
+
+struct EicHmacSha256Ctx {
+  uint8_t reserved[EIC_HMAC_SHA256_CONTEXT_SIZE];
+};
+typedef struct EicHmacSha256Ctx EicHmacSha256Ctx;
+
+#ifdef EIC_DEBUG
+// Debug macro. Don't include a new-line in message.
+//
+#define eicDebug(...)                        \
+  do {                                       \
+    eicPrint("%s:%d: ", __FILE__, __LINE__); \
+    eicPrint(__VA_ARGS__);                   \
+    eicPrint("\n");                          \
+  } while (0)
+#else
+#define eicDebug(...) \
+  do {                \
+  } while (0)
+#endif
+
+// Prints message which should include new-line character. Can be no-op.
+//
+// Don't use this from code, use eicDebug() instead.
+//
+#ifdef EIC_DEBUG
+void eicPrint(const char* format, ...);
+#else
+inline void eicPrint(const char*, ...) {}
+#endif
+
+// Dumps data as pretty-printed hex. Can be no-op.
+//
+#ifdef EIC_DEBUG
+void eicHexdump(const char* message, const uint8_t* data, size_t dataSize);
+#else
+inline void eicHexdump(const char*, const uint8_t*, size_t) {}
+#endif
+
+// Pretty-prints encoded CBOR. Can be no-op.
+//
+// If a byte-string is larger than |maxBStrSize| its contents will not be
+// printed, instead the value of the form "<bstr size=1099016
+// sha1=ef549cca331f73dfae2090e6a37c04c23f84b07b>" will be printed. Pass zero
+// for |maxBStrSize| to disable this.
+//
+#ifdef EIC_DEBUG
+void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize,
+                        size_t maxBStrSize);
+#else
+inline void eicCborPrettyPrint(const uint8_t*, size_t, size_t) {}
+#endif
+
+// Memory setting, see memset(3).
+void* eicMemSet(void* s, int c, size_t n);
+
+// Memory copying, see memcpy(3).
+void* eicMemCpy(void* dest, const void* src, size_t n);
+
+// String length, see strlen(3).
+size_t eicStrLen(const char* s);
+
+// Memory compare, see CRYPTO_memcmp(3SSL)
+//
+// It takes an amount of time dependent on len, but independent of the contents
+// of the memory regions pointed to by s1 and s2.
+//
+int eicCryptoMemCmp(const void* s1, const void* s2, size_t n);
+
+// Random number generation.
+bool eicOpsRandom(uint8_t* buf, size_t numBytes);
+
+// If |testCredential| is true, returns the 128-bit AES Hardware-Bound Key (16
+// bytes).
+//
+// Otherwise returns all zeroes (16 bytes).
+//
+const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential);
+
+// Encrypts |data| with |key| and |additionalAuthenticatedData| using |nonce|,
+// returns the resulting (nonce || ciphertext || tag) in |encryptedData| which
+// must be of size |dataSize| + 28.
+bool eicOpsEncryptAes128Gcm(
+    const uint8_t* key,    // Must be 16 bytes
+    const uint8_t* nonce,  // Must be 12 bytes
+    const uint8_t* data,   // May be NULL if size is 0
+    size_t dataSize,
+    const uint8_t* additionalAuthenticationData,  // May be NULL if size is 0
+    size_t additionalAuthenticationDataSize, uint8_t* encryptedData);
+
+// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|,
+// returns resulting plaintext in |data| must be of size |encryptedDataSize|
+// - 28.
+//
+// The format of |encryptedData| must be as specified in the
+// encryptAes128Gcm() function.
+bool eicOpsDecryptAes128Gcm(const uint8_t* key,  // Must be 16 bytes
+                            const uint8_t* encryptedData,
+                            size_t encryptedDataSize,
+                            const uint8_t* additionalAuthenticationData,
+                            size_t additionalAuthenticationDataSize,
+                            uint8_t* data);
+
+// Creates an EC key using the P-256 curve. The private key is written to
+// |privateKey|. The public key is written to |publicKey|.
+//
+bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+                       uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]);
+
+// Generates CredentialKey plus an attestation certificate.
+//
+// The attestation certificate will be signed by the attestation keys the secure
+// area has been provisioned with. The given |challenge| and |applicationId|
+// will be used as will |testCredential|.
+//
+// The generated certificate will be in X.509 format and returned in |cert|
+// and |certSize| must be set to the size of this array and this function will
+// set it to the size of the certification chain on successfully return.
+//
+// This may return either a single certificate or an entire certificate
+// chain. If it returns only a single certificate, the implementation of
+// SecureHardwareProvisioningProxy::createCredentialKey() should amend the
+// remainder of the certificate chain on the HAL side.
+//
+bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+                               const uint8_t* challenge, size_t challengeSize,
+                               const uint8_t* applicationId,
+                               size_t applicationIdSize, bool testCredential,
+                               uint8_t* cert,
+                               size_t* certSize);  // inout
+
+// Generate an X.509 certificate for the key identified by |publicKey| which
+// must be of the form returned by eicOpsCreateEcKey().
+//
+// If proofOfBinding is not NULL, it will be included as an OCTET_STRING
+// X.509 extension at OID 1.3.6.1.4.1.11129.2.1.26.
+//
+// The certificate will be signed by the key identified by |signingKey| which
+// must be of the form returned by eicOpsCreateEcKey().
+//
+bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+                     const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE],
+                     unsigned int serial, const char* issuerName,
+                     const char* subjectName, time_t validityNotBefore,
+                     time_t validityNotAfter, const uint8_t* proofOfBinding,
+                     size_t proofOfBindingSize, uint8_t* cert,
+                     size_t* certSize);  // inout
+
+// Uses |privateKey| to create an ECDSA signature of some data (the SHA-256 must
+// be given by |digestOfData|). Returns the signature in |signature|.
+//
+bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+                 const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE],
+                 uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+// Performs Elliptic Curve Diffie-Helman.
+//
+bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+                const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+                uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]);
+
+// Performs HKDF.
+//
+bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize,
+                const uint8_t* salt, size_t saltSize, const uint8_t* info,
+                size_t infoSize, uint8_t* output, size_t outputSize);
+
+// SHA-256 functions.
+void eicOpsSha256Init(EicSha256Ctx* ctx);
+void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len);
+void eicOpsSha256Final(EicSha256Ctx* ctx,
+                       uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+// HMAC SHA-256 functions.
+void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key,
+                          size_t keySize);
+void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data,
+                            size_t len);
+void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx,
+                           uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+// Extracts the public key in the given X.509 certificate.
+//
+// If the key is not an EC key, this function fails.
+//
+// Otherwise the public key is stored in uncompressed form in |publicKey| which
+// size should be set in |publicKeySize|. On successful return |publicKeySize|
+// is set to the length of the key. If there is not enough space, the function
+// fails.
+//
+// (The public key returned is not necessarily a P-256 key, even if it is note
+// that its size is not EIC_P256_PUBLIC_KEY_SIZE because of the leading 0x04.)
+//
+bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize,
+                            uint8_t* publicKey, size_t* publicKeySize);
+
+// Checks that the X.509 certificate given by |x509Cert| is signed by the public
+// key given by |publicKey| which must be an EC key in uncompressed form (e.g.
+// same formatt as returned by eicOpsX509GetPublicKey()).
+//
+bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert,
+                                     size_t x509CertSize,
+                                     const uint8_t* publicKey,
+                                     size_t publicKeySize);
+
+// Checks that |signature| is a signature of some data (given by |digest|),
+// signed by the public key given by |publicKey|.
+//
+// The key must be an EC key in uncompressed form (e.g.  same format as returned
+// by eicOpsX509GetPublicKey()).
+//
+// The format of the signature is the same encoding as the 'signature' field of
+// COSE_Sign1 - that is, it's the R and S integers both with the same length as
+// the key-size.
+//
+// The size of digest must match the size of the key.
+//
+bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize,
+                                    const uint8_t* signature,
+                                    size_t signatureSize,
+                                    const uint8_t* publicKey,
+                                    size_t publicKeySize);
+
+// Validates that the passed in data constitutes a valid auth- and verification
+// tokens.
+//
+bool eicOpsValidateAuthToken(
+    uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+    int hardwareAuthenticatorType, uint64_t timeStamp, const uint8_t* mac,
+    size_t macSize, uint64_t verificationTokenChallenge,
+    uint64_t verificationTokenTimeStamp, int verificationTokenSecurityLevel,
+    const uint8_t* verificationTokenMac, size_t verificationTokenMacSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
diff --git a/guest/hals/identity/libeic/EicOpsImpl.cc b/guest/hals/identity/libeic/EicOpsImpl.cc
new file mode 100644
index 0000000..0921c72
--- /dev/null
+++ b/guest/hals/identity/libeic/EicOpsImpl.cc
@@ -0,0 +1,546 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "EicOpsImpl"
+
+#include <optional>
+#include <tuple>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <string.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <openssl/sha.h>
+
+#include <openssl/aes.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include "EicOps.h"
+
+using ::std::map;
+using ::std::optional;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+void* eicMemSet(void* s, int c, size_t n) { return memset(s, c, n); }
+
+void* eicMemCpy(void* dest, const void* src, size_t n) {
+  return memcpy(dest, src, n);
+}
+
+size_t eicStrLen(const char* s) { return strlen(s); }
+
+int eicCryptoMemCmp(const void* s1, const void* s2, size_t n) {
+  return CRYPTO_memcmp(s1, s2, n);
+}
+
+void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key,
+                          size_t keySize) {
+  HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+  HMAC_CTX_init(realCtx);
+  if (HMAC_Init_ex(realCtx, key, keySize, EVP_sha256(), nullptr /* impl */) !=
+      1) {
+    LOG(ERROR) << "Error initializing HMAC_CTX";
+  }
+}
+
+void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data,
+                            size_t len) {
+  HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+  if (HMAC_Update(realCtx, data, len) != 1) {
+    LOG(ERROR) << "Error updating HMAC_CTX";
+  }
+}
+
+void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx,
+                           uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+  HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+  unsigned int size = 0;
+  if (HMAC_Final(realCtx, digest, &size) != 1) {
+    LOG(ERROR) << "Error finalizing HMAC_CTX";
+  }
+  if (size != EIC_SHA256_DIGEST_SIZE) {
+    LOG(ERROR) << "Expected 32 bytes from HMAC_Final, got " << size;
+  }
+}
+
+void eicOpsSha256Init(EicSha256Ctx* ctx) {
+  SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+  SHA256_Init(realCtx);
+}
+
+void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len) {
+  SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+  SHA256_Update(realCtx, data, len);
+}
+
+void eicOpsSha256Final(EicSha256Ctx* ctx,
+                       uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+  SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+  SHA256_Final(digest, realCtx);
+}
+
+bool eicOpsRandom(uint8_t* buf, size_t numBytes) {
+  optional<vector<uint8_t>> bytes =
+      ::android::hardware::identity::support::getRandom(numBytes);
+  if (!bytes.has_value()) {
+    return false;
+  }
+  memcpy(buf, bytes.value().data(), numBytes);
+  return true;
+}
+
+bool eicOpsEncryptAes128Gcm(
+    const uint8_t* key,    // Must be 16 bytes
+    const uint8_t* nonce,  // Must be 12 bytes
+    const uint8_t* data,   // May be NULL if size is 0
+    size_t dataSize,
+    const uint8_t* additionalAuthenticationData,  // May be NULL if size is 0
+    size_t additionalAuthenticationDataSize, uint8_t* encryptedData) {
+  vector<uint8_t> cppKey;
+  cppKey.resize(16);
+  memcpy(cppKey.data(), key, 16);
+
+  vector<uint8_t> cppData;
+  cppData.resize(dataSize);
+  if (dataSize > 0) {
+    memcpy(cppData.data(), data, dataSize);
+  }
+
+  vector<uint8_t> cppAAD;
+  cppAAD.resize(additionalAuthenticationDataSize);
+  if (additionalAuthenticationDataSize > 0) {
+    memcpy(cppAAD.data(), additionalAuthenticationData,
+           additionalAuthenticationDataSize);
+  }
+
+  vector<uint8_t> cppNonce;
+  cppNonce.resize(12);
+  memcpy(cppNonce.data(), nonce, 12);
+
+  optional<vector<uint8_t>> cppEncryptedData =
+      android::hardware::identity::support::encryptAes128Gcm(cppKey, cppNonce,
+                                                             cppData, cppAAD);
+  if (!cppEncryptedData.has_value()) {
+    return false;
+  }
+
+  memcpy(encryptedData, cppEncryptedData.value().data(),
+         cppEncryptedData.value().size());
+  return true;
+}
+
+// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|,
+// returns resulting plaintext in |data| must be of size |encryptedDataSize|
+// - 28.
+//
+// The format of |encryptedData| must be as specified in the
+// encryptAes128Gcm() function.
+bool eicOpsDecryptAes128Gcm(const uint8_t* key,  // Must be 16 bytes
+                            const uint8_t* encryptedData,
+                            size_t encryptedDataSize,
+                            const uint8_t* additionalAuthenticationData,
+                            size_t additionalAuthenticationDataSize,
+                            uint8_t* data) {
+  vector<uint8_t> keyVec;
+  keyVec.resize(16);
+  memcpy(keyVec.data(), key, 16);
+
+  vector<uint8_t> encryptedDataVec;
+  encryptedDataVec.resize(encryptedDataSize);
+  if (encryptedDataSize > 0) {
+    memcpy(encryptedDataVec.data(), encryptedData, encryptedDataSize);
+  }
+
+  vector<uint8_t> aadVec;
+  aadVec.resize(additionalAuthenticationDataSize);
+  if (additionalAuthenticationDataSize > 0) {
+    memcpy(aadVec.data(), additionalAuthenticationData,
+           additionalAuthenticationDataSize);
+  }
+
+  optional<vector<uint8_t>> decryptedDataVec =
+      android::hardware::identity::support::decryptAes128Gcm(
+          keyVec, encryptedDataVec, aadVec);
+  if (!decryptedDataVec.has_value()) {
+    eicDebug("Error decrypting data");
+    return false;
+  }
+  if (decryptedDataVec.value().size() != encryptedDataSize - 28) {
+    eicDebug("Decrypted data is size %zd, expected %zd",
+             decryptedDataVec.value().size(), encryptedDataSize - 28);
+    return false;
+  }
+
+  if (decryptedDataVec.value().size() > 0) {
+    memcpy(data, decryptedDataVec.value().data(),
+           decryptedDataVec.value().size());
+  }
+  return true;
+}
+
+bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+                       uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]) {
+  optional<vector<uint8_t>> keyPair =
+      android::hardware::identity::support::createEcKeyPair();
+  if (!keyPair) {
+    eicDebug("Error creating EC keypair");
+    return false;
+  }
+  optional<vector<uint8_t>> privKey =
+      android::hardware::identity::support::ecKeyPairGetPrivateKey(
+          keyPair.value());
+  if (!privKey) {
+    eicDebug("Error extracting private key");
+    return false;
+  }
+  if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) {
+    eicDebug("Private key is %zd bytes, expected %zd", privKey.value().size(),
+             (size_t)EIC_P256_PRIV_KEY_SIZE);
+    return false;
+  }
+
+  optional<vector<uint8_t>> pubKey =
+      android::hardware::identity::support::ecKeyPairGetPublicKey(
+          keyPair.value());
+  if (!pubKey) {
+    eicDebug("Error extracting public key");
+    return false;
+  }
+  // ecKeyPairGetPublicKey() returns 0x04 | x | y, we don't want the leading
+  // 0x04.
+  if (pubKey.value().size() != EIC_P256_PUB_KEY_SIZE + 1) {
+    eicDebug("Public key is %zd bytes long, expected %zd",
+             pubKey.value().size(), (size_t)EIC_P256_PRIV_KEY_SIZE + 1);
+    return false;
+  }
+
+  memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE);
+  memcpy(publicKey, pubKey.value().data() + 1, EIC_P256_PUB_KEY_SIZE);
+
+  return true;
+}
+
+bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+                               const uint8_t* challenge, size_t challengeSize,
+                               const uint8_t* applicationId,
+                               size_t applicationIdSize, bool testCredential,
+                               uint8_t* cert, size_t* certSize) {
+  vector<uint8_t> challengeVec(challengeSize);
+  memcpy(challengeVec.data(), challenge, challengeSize);
+
+  vector<uint8_t> applicationIdVec(applicationIdSize);
+  memcpy(applicationIdVec.data(), applicationId, applicationIdSize);
+
+  optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> ret =
+      android::hardware::identity::support::createEcKeyPairAndAttestation(
+          challengeVec, applicationIdVec, testCredential);
+  if (!ret) {
+    eicDebug("Error generating CredentialKey and attestation");
+    return false;
+  }
+
+  // Extract certificate chain.
+  vector<uint8_t> flatChain =
+      android::hardware::identity::support::certificateChainJoin(
+          ret.value().second);
+  if (*certSize < flatChain.size()) {
+    eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes",
+             *certSize, flatChain.size());
+    return false;
+  }
+  memcpy(cert, flatChain.data(), flatChain.size());
+  *certSize = flatChain.size();
+
+  // Extract private key.
+  optional<vector<uint8_t>> privKey =
+      android::hardware::identity::support::ecKeyPairGetPrivateKey(
+          ret.value().first);
+  if (!privKey) {
+    eicDebug("Error extracting private key");
+    return false;
+  }
+  if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) {
+    eicDebug("Private key is %zd bytes, expected %zd", privKey.value().size(),
+             (size_t)EIC_P256_PRIV_KEY_SIZE);
+    return false;
+  }
+
+  memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE);
+
+  return true;
+}
+
+bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+                     const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE],
+                     unsigned int serial, const char* issuerName,
+                     const char* subjectName, time_t validityNotBefore,
+                     time_t validityNotAfter, const uint8_t* proofOfBinding,
+                     size_t proofOfBindingSize, uint8_t* cert,
+                     size_t* certSize) {  // inout
+  vector<uint8_t> signingKeyVec(EIC_P256_PRIV_KEY_SIZE);
+  memcpy(signingKeyVec.data(), signingKey, EIC_P256_PRIV_KEY_SIZE);
+
+  vector<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1);
+  pubKeyVec[0] = 0x04;
+  memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE);
+
+  string serialDecimal = android::base::StringPrintf("%d", serial);
+
+  map<string, vector<uint8_t>> extensions;
+  if (proofOfBinding != nullptr) {
+    vector<uint8_t> proofOfBindingVec(proofOfBinding,
+                                      proofOfBinding + proofOfBindingSize);
+    extensions["1.3.6.1.4.1.11129.2.1.26"] = proofOfBindingVec;
+  }
+
+  optional<vector<uint8_t>> certVec =
+      android::hardware::identity::support::ecPublicKeyGenerateCertificate(
+          pubKeyVec, signingKeyVec, serialDecimal, issuerName, subjectName,
+          validityNotBefore, validityNotAfter, extensions);
+  if (!certVec) {
+    eicDebug("Error generating certificate");
+    return false;
+  }
+
+  if (*certSize < certVec.value().size()) {
+    eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes",
+             *certSize, certVec.value().size());
+    return false;
+  }
+  memcpy(cert, certVec.value().data(), certVec.value().size());
+  *certSize = certVec.value().size();
+
+  return true;
+}
+
+bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+                 const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE],
+                 uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+  vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE);
+  memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE);
+
+  vector<uint8_t> digestVec(EIC_SHA256_DIGEST_SIZE);
+  memcpy(digestVec.data(), digestOfData, EIC_SHA256_DIGEST_SIZE);
+
+  optional<vector<uint8_t>> derSignature =
+      android::hardware::identity::support::signEcDsaDigest(privKeyVec,
+                                                            digestVec);
+  if (!derSignature) {
+    eicDebug("Error signing data");
+    return false;
+  }
+
+  ECDSA_SIG* sig;
+  const unsigned char* p = derSignature.value().data();
+  sig = d2i_ECDSA_SIG(nullptr, &p, derSignature.value().size());
+  if (sig == nullptr) {
+    eicDebug("Error decoding DER signature");
+    return false;
+  }
+
+  if (BN_bn2binpad(sig->r, signature, 32) != 32) {
+    eicDebug("Error encoding r");
+    return false;
+  }
+  if (BN_bn2binpad(sig->s, signature + 32, 32) != 32) {
+    eicDebug("Error encoding s");
+    return false;
+  }
+
+  return true;
+}
+
+static const uint8_t hbkTest[16] = {0};
+static const uint8_t hbkReal[16] = {0, 1, 2,  3,  4,  5,  6,  7,
+                                    8, 9, 10, 11, 12, 13, 14, 15};
+
+const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential) {
+  if (testCredential) {
+    return hbkTest;
+  }
+  return hbkReal;
+}
+
+bool eicOpsValidateAuthToken(uint64_t /* challenge */,
+                             uint64_t /* secureUserId */,
+                             uint64_t /* authenticatorId */,
+                             int /* hardwareAuthenticatorType */,
+                             uint64_t /* timeStamp */, const uint8_t* /* mac */,
+                             size_t /* macSize */,
+                             uint64_t /* verificationTokenChallenge */,
+                             uint64_t /* verificationTokenTimeStamp */,
+                             int /* verificationTokenSecurityLevel */,
+                             const uint8_t* /* verificationTokenMac */,
+                             size_t /* verificationTokenMacSize */) {
+  // Here's where we would validate the passed-in |authToken| to assure
+  // ourselves that it comes from the e.g. biometric hardware and wasn't made up
+  // by an attacker.
+  //
+  // However this involves calculating the MAC which requires access to the to
+  // a pre-shared key which we don't have...
+  //
+  return true;
+}
+
+bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize,
+                            uint8_t* publicKey, size_t* publicKeySize) {
+  vector<uint8_t> chain;
+  chain.resize(x509CertSize);
+  memcpy(chain.data(), x509Cert, x509CertSize);
+  optional<vector<uint8_t>> res =
+      android::hardware::identity::support::certificateChainGetTopMostKey(
+          chain);
+  if (!res) {
+    return false;
+  }
+  if (res.value().size() > *publicKeySize) {
+    eicDebug("Public key size is %zd but buffer only has room for %zd bytes",
+             res.value().size(), *publicKeySize);
+    return false;
+  }
+  *publicKeySize = res.value().size();
+  memcpy(publicKey, res.value().data(), *publicKeySize);
+  eicDebug("Extracted %zd bytes public key from %zd bytes X.509 cert",
+           *publicKeySize, x509CertSize);
+  return true;
+}
+
+bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert,
+                                     size_t x509CertSize,
+                                     const uint8_t* publicKey,
+                                     size_t publicKeySize) {
+  vector<uint8_t> certVec(x509Cert, x509Cert + x509CertSize);
+  vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize);
+  return android::hardware::identity::support::certificateSignedByPublicKey(
+      certVec, publicKeyVec);
+}
+
+bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize,
+                                    const uint8_t* signature,
+                                    size_t signatureSize,
+                                    const uint8_t* publicKey,
+                                    size_t publicKeySize) {
+  vector<uint8_t> digestVec(digest, digest + digestSize);
+  vector<uint8_t> signatureVec(signature, signature + signatureSize);
+  vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize);
+
+  vector<uint8_t> derSignature;
+  if (!android::hardware::identity::support::ecdsaSignatureCoseToDer(
+          signatureVec, derSignature)) {
+    LOG(ERROR) << "Error convering signature to DER format";
+    return false;
+  }
+
+  if (!android::hardware::identity::support::checkEcDsaSignature(
+          digestVec, derSignature, publicKeyVec)) {
+    LOG(ERROR) << "Signature check failed";
+    return false;
+  }
+  return true;
+}
+
+bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+                const uint8_t privateKey[EIC_P256_PUB_KEY_SIZE],
+                uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]) {
+  vector<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1);
+  pubKeyVec[0] = 0x04;
+  memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE);
+
+  vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE);
+  memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE);
+
+  optional<vector<uint8_t>> shared =
+      android::hardware::identity::support::ecdh(pubKeyVec, privKeyVec);
+  if (!shared) {
+    LOG(ERROR) << "Error performing ECDH";
+    return false;
+  }
+  if (shared.value().size() != EIC_P256_COORDINATE_SIZE) {
+    LOG(ERROR) << "Unexpected size of shared secret " << shared.value().size()
+               << " expected " << EIC_P256_COORDINATE_SIZE << " bytes";
+    return false;
+  }
+  memcpy(sharedSecret, shared.value().data(), EIC_P256_COORDINATE_SIZE);
+  return true;
+}
+
+bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize,
+                const uint8_t* salt, size_t saltSize, const uint8_t* info,
+                size_t infoSize, uint8_t* output, size_t outputSize) {
+  vector<uint8_t> sharedSecretVec(sharedSecretSize);
+  memcpy(sharedSecretVec.data(), sharedSecret, sharedSecretSize);
+  vector<uint8_t> saltVec(saltSize);
+  memcpy(saltVec.data(), salt, saltSize);
+  vector<uint8_t> infoVec(infoSize);
+  memcpy(infoVec.data(), info, infoSize);
+
+  optional<vector<uint8_t>> result = android::hardware::identity::support::hkdf(
+      sharedSecretVec, saltVec, infoVec, outputSize);
+  if (!result) {
+    LOG(ERROR) << "Error performing HKDF";
+    return false;
+  }
+  if (result.value().size() != outputSize) {
+    LOG(ERROR) << "Unexpected size of HKDF " << result.value().size()
+               << " expected " << outputSize;
+    return false;
+  }
+  memcpy(output, result.value().data(), outputSize);
+  return true;
+}
+
+#ifdef EIC_DEBUG
+
+void eicPrint(const char* format, ...) {
+  va_list args;
+  va_start(args, format);
+  vfprintf(stderr, format, args);
+  va_end(args);
+}
+
+void eicHexdump(const char* message, const uint8_t* data, size_t dataSize) {
+  vector<uint8_t> dataVec(dataSize);
+  memcpy(dataVec.data(), data, dataSize);
+  android::hardware::identity::support::hexdump(message, dataVec);
+}
+
+void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize,
+                        size_t maxBStrSize) {
+  vector<uint8_t> cborDataVec(cborDataSize);
+  memcpy(cborDataVec.data(), cborData, cborDataSize);
+  string str = android::hardware::identity::support::cborPrettyPrint(
+      cborDataVec, maxBStrSize, {});
+  fprintf(stderr, "%s\n", str.c_str());
+}
+
+#endif  // EIC_DEBUG
diff --git a/guest/hals/identity/libeic/EicOpsImpl.h b/guest/hals/identity/libeic/EicOpsImpl.h
new file mode 100644
index 0000000..333cdce
--- /dev/null
+++ b/guest/hals/identity/libeic/EicOpsImpl.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+// Add whatever includes are needed for definitions below.
+//
+
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Set the following defines to match the implementation of the supplied
+// eicOps*() operations. See EicOps.h for details.
+//
+
+#define EIC_SHA256_CONTEXT_SIZE sizeof(SHA256_CTX)
+
+#define EIC_HMAC_SHA256_CONTEXT_SIZE sizeof(HMAC_CTX)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_HARDWARE_IDENTITY_EMBEDDED_IC_H
diff --git a/guest/hals/identity/libeic/EicPresentation.c b/guest/hals/identity/libeic/EicPresentation.c
new file mode 100644
index 0000000..520c2c2
--- /dev/null
+++ b/guest/hals/identity/libeic/EicPresentation.c
@@ -0,0 +1,916 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EicPresentation.h"
+#include "EicCommon.h"
+
+#include <inttypes.h>
+
+bool eicPresentationInit(EicPresentation* ctx, bool testCredential,
+                         const char* docType, size_t docTypeLength,
+                         const uint8_t* encryptedCredentialKeys,
+                         size_t encryptedCredentialKeysSize) {
+  uint8_t credentialKeys[EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101];
+  bool expectPopSha256 = false;
+
+  // For feature version 202009 it's 52 bytes long and for feature version
+  // 202101 it's 86 bytes (the additional data is the ProofOfProvisioning
+  // SHA-256). We need to support loading all feature versions.
+  //
+  if (encryptedCredentialKeysSize ==
+      EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202009 + 28) {
+    /* do nothing */
+  } else if (encryptedCredentialKeysSize ==
+             EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101 + 28) {
+    expectPopSha256 = true;
+  } else {
+    eicDebug("Unexpected size %zd for encryptedCredentialKeys",
+             encryptedCredentialKeysSize);
+    return false;
+  }
+
+  eicMemSet(ctx, '\0', sizeof(EicPresentation));
+
+  if (!eicOpsDecryptAes128Gcm(
+          eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
+          encryptedCredentialKeysSize,
+          // DocType is the additionalAuthenticatedData
+          (const uint8_t*)docType, docTypeLength, credentialKeys)) {
+    eicDebug("Error decrypting CredentialKeys");
+    return false;
+  }
+
+  // It's supposed to look like this;
+  //
+  // Feature version 202009:
+  //
+  //         CredentialKeys = [
+  //              bstr,   ; storageKey, a 128-bit AES key
+  //              bstr,   ; credentialPrivKey, the private key for credentialKey
+  //         ]
+  //
+  // Feature version 202101:
+  //
+  //         CredentialKeys = [
+  //              bstr,   ; storageKey, a 128-bit AES key
+  //              bstr,   ; credentialPrivKey, the private key for credentialKey
+  //              bstr    ; proofOfProvisioning SHA-256
+  //         ]
+  //
+  // where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and
+  // proofOfProvisioning SHA-256 is 32 bytes.
+  //
+  if (credentialKeys[0] !=
+          (expectPopSha256 ? 0x83 : 0x82) ||  // array of two or three elements
+      credentialKeys[1] != 0x50 ||            // 16-byte bstr
+      credentialKeys[18] != 0x58 ||
+      credentialKeys[19] != 0x20) {  // 32-byte bstr
+    eicDebug("Invalid CBOR for CredentialKeys");
+    return false;
+  }
+  if (expectPopSha256) {
+    if (credentialKeys[52] != 0x58 ||
+        credentialKeys[53] != 0x20) {  // 32-byte bstr
+      eicDebug("Invalid CBOR for CredentialKeys");
+      return false;
+    }
+  }
+  eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE);
+  eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20,
+            EIC_P256_PRIV_KEY_SIZE);
+  ctx->testCredential = testCredential;
+  if (expectPopSha256) {
+    eicMemCpy(ctx->proofOfProvisioningSha256, credentialKeys + 54,
+              EIC_SHA256_DIGEST_SIZE);
+  }
+  return true;
+}
+
+bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx,
+                                           const char* docType,
+                                           size_t docTypeLength, time_t now,
+                                           uint8_t* publicKeyCert,
+                                           size_t* publicKeyCertSize,
+                                           uint8_t signingKeyBlob[60]) {
+  uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
+  uint8_t signingKeyPub[EIC_P256_PUB_KEY_SIZE];
+  uint8_t cborBuf[64];
+
+  // Generate the ProofOfBinding CBOR to include in the X.509 certificate in
+  // IdentityCredentialAuthenticationKeyExtension CBOR. This CBOR is defined
+  // by the following CDDL
+  //
+  //   ProofOfBinding = [
+  //     "ProofOfBinding",
+  //     bstr,                  // Contains the SHA-256 of ProofOfProvisioning
+  //   ]
+  //
+  // This array may grow in the future if other information needs to be
+  // conveyed.
+  //
+  // The bytes of ProofOfBinding is is represented as an OCTET_STRING
+  // and stored at OID 1.3.6.1.4.1.11129.2.1.26.
+  //
+
+  EicCbor cbor;
+  eicCborInit(&cbor, cborBuf, sizeof cborBuf);
+  eicCborAppendArray(&cbor, 2);
+  eicCborAppendStringZ(&cbor, "ProofOfBinding");
+  eicCborAppendByteString(&cbor, ctx->proofOfProvisioningSha256,
+                          EIC_SHA256_DIGEST_SIZE);
+  if (cbor.size > sizeof(cborBuf)) {
+    eicDebug("Exceeded buffer size");
+    return false;
+  }
+  const uint8_t* proofOfBinding = cborBuf;
+  size_t proofOfBindingSize = cbor.size;
+
+  if (!eicOpsCreateEcKey(signingKeyPriv, signingKeyPub)) {
+    eicDebug("Error creating signing key");
+    return false;
+  }
+
+  const int secondsInOneYear = 365 * 24 * 60 * 60;
+  time_t validityNotBefore = now;
+  time_t validityNotAfter = now + secondsInOneYear;  // One year from now.
+  if (!eicOpsSignEcKey(
+          signingKeyPub, ctx->credentialPrivateKey, 1,
+          "Android Identity Credential Key",                 // issuer CN
+          "Android Identity Credential Authentication Key",  // subject CN
+          validityNotBefore, validityNotAfter, proofOfBinding,
+          proofOfBindingSize, publicKeyCert, publicKeyCertSize)) {
+    eicDebug("Error creating certificate for signing key");
+    return false;
+  }
+
+  uint8_t nonce[12];
+  if (!eicOpsRandom(nonce, 12)) {
+    eicDebug("Error getting random");
+    return false;
+  }
+  if (!eicOpsEncryptAes128Gcm(
+          ctx->storageKey, nonce, signingKeyPriv, sizeof(signingKeyPriv),
+          // DocType is the additionalAuthenticatedData
+          (const uint8_t*)docType, docTypeLength, signingKeyBlob)) {
+    eicDebug("Error encrypting signing key");
+    return false;
+  }
+
+  return true;
+}
+
+bool eicPresentationCreateEphemeralKeyPair(
+    EicPresentation* ctx, uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]) {
+  uint8_t ephemeralPublicKey[EIC_P256_PUB_KEY_SIZE];
+  if (!eicOpsCreateEcKey(ctx->ephemeralPrivateKey, ephemeralPublicKey)) {
+    eicDebug("Error creating ephemeral key");
+    return false;
+  }
+  eicMemCpy(ephemeralPrivateKey, ctx->ephemeralPrivateKey,
+            EIC_P256_PRIV_KEY_SIZE);
+  return true;
+}
+
+bool eicPresentationCreateAuthChallenge(EicPresentation* ctx,
+                                        uint64_t* authChallenge) {
+  do {
+    if (!eicOpsRandom((uint8_t*)&(ctx->authChallenge), sizeof(uint64_t))) {
+      eicDebug("Failed generating random challenge");
+      return false;
+    }
+  } while (ctx->authChallenge == 0);
+  eicDebug("Created auth challenge %" PRIu64, ctx->authChallenge);
+  *authChallenge = ctx->authChallenge;
+  return true;
+}
+
+// From "COSE Algorithms" registry
+//
+#define COSE_ALG_ECDSA_256 -7
+
+bool eicPresentationValidateRequestMessage(
+    EicPresentation* ctx, const uint8_t* sessionTranscript,
+    size_t sessionTranscriptSize, const uint8_t* requestMessage,
+    size_t requestMessageSize, int coseSignAlg,
+    const uint8_t* readerSignatureOfToBeSigned,
+    size_t readerSignatureOfToBeSignedSize) {
+  if (ctx->readerPublicKeySize == 0) {
+    eicDebug("No public key for reader");
+    return false;
+  }
+
+  // Right now we only support ECDSA with SHA-256 (e.g. ES256).
+  //
+  if (coseSignAlg != COSE_ALG_ECDSA_256) {
+    eicDebug(
+        "COSE Signature algorithm for reader signature is %d, "
+        "only ECDSA with SHA-256 is supported right now",
+        coseSignAlg);
+    return false;
+  }
+
+  // What we're going to verify is the COSE ToBeSigned structure which
+  // looks like the following:
+  //
+  //   Sig_structure = [
+  //     context : "Signature" / "Signature1" / "CounterSignature",
+  //     body_protected : empty_or_serialized_map,
+  //     ? sign_protected : empty_or_serialized_map,
+  //     external_aad : bstr,
+  //     payload : bstr
+  //   ]
+  //
+  // So we're going to build that CBOR...
+  //
+  EicCbor cbor;
+  eicCborInit(&cbor, NULL, 0);
+  eicCborAppendArray(&cbor, 4);
+  eicCborAppendStringZ(&cbor, "Signature1");
+
+  // The COSE Encoded protected headers is just a single field with
+  // COSE_LABEL_ALG (1) -> coseSignAlg (e.g. -7). For simplicitly we just
+  // hard-code the CBOR encoding:
+  static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+  eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+                          sizeof(coseEncodedProtectedHeaders));
+
+  // External_aad is the empty bstr
+  static const uint8_t externalAad[0] = {};
+  eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+  // For the payload, the _encoded_ form follows here. We handle this by simply
+  // opening a bstr, and then writing the CBOR. This requires us to know the
+  // size of said bstr, ahead of time... the CBOR to be written is
+  //
+  //   ReaderAuthentication = [
+  //      "ReaderAuthentication",
+  //      SessionTranscript,
+  //      ItemsRequestBytes
+  //   ]
+  //
+  //   ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
+  //
+  //   ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication)
+  //
+  // which is easily calculated below
+  //
+  size_t calculatedSize = 0;
+  calculatedSize += 1;  // Array of size 3
+  calculatedSize += 1;  // "ReaderAuthentication" less than 24 bytes
+  calculatedSize +=
+      sizeof("ReaderAuthentication") - 1;   // Don't include trailing NUL
+  calculatedSize += sessionTranscriptSize;  // Already CBOR encoded
+  calculatedSize += 2;  // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+  calculatedSize += 1 + eicCborAdditionalLengthBytesFor(requestMessageSize);
+  calculatedSize += requestMessageSize;
+
+  // However note that we're authenticating ReaderAuthenticationBytes which
+  // is a tagged bstr of the bytes of ReaderAuthentication. So need to get
+  // that in front.
+  size_t rabCalculatedSize = 0;
+  rabCalculatedSize +=
+      2;  // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+  rabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize);
+  rabCalculatedSize += calculatedSize;
+
+  // Begin the bytestring for ReaderAuthenticationBytes;
+  eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, rabCalculatedSize);
+
+  eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+
+  // Begins the bytestring for ReaderAuthentication;
+  eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);
+
+  // And now that we know the size, let's fill it in...
+  //
+  size_t payloadOffset = cbor.size;
+  eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, 3);
+  eicCborAppendStringZ(&cbor, "ReaderAuthentication");
+  eicCborAppend(&cbor, sessionTranscript, sessionTranscriptSize);
+  eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+  eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, requestMessageSize);
+  eicCborAppend(&cbor, requestMessage, requestMessageSize);
+
+  if (cbor.size != payloadOffset + calculatedSize) {
+    eicDebug("CBOR size is %zd but we expected %zd", cbor.size,
+             payloadOffset + calculatedSize);
+    return false;
+  }
+  uint8_t toBeSignedDigest[EIC_SHA256_DIGEST_SIZE];
+  eicCborFinal(&cbor, toBeSignedDigest);
+
+  if (!eicOpsEcDsaVerifyWithPublicKey(
+          toBeSignedDigest, EIC_SHA256_DIGEST_SIZE, readerSignatureOfToBeSigned,
+          readerSignatureOfToBeSignedSize, ctx->readerPublicKey,
+          ctx->readerPublicKeySize)) {
+    eicDebug("Request message is not signed by public key");
+    return false;
+  }
+  ctx->requestMessageValidated = true;
+  return true;
+}
+
+// Validates the next certificate in the reader certificate chain.
+bool eicPresentationPushReaderCert(EicPresentation* ctx,
+                                   const uint8_t* certX509,
+                                   size_t certX509Size) {
+  // If we had a previous certificate, use its public key to validate this
+  // certificate.
+  if (ctx->readerPublicKeySize > 0) {
+    if (!eicOpsX509CertSignedByPublicKey(certX509, certX509Size,
+                                         ctx->readerPublicKey,
+                                         ctx->readerPublicKeySize)) {
+      eicDebug(
+          "Certificate is not signed by public key in the previous "
+          "certificate");
+      return false;
+    }
+  }
+
+  // Store the key of this certificate, this is used to validate the next
+  // certificate and also ACPs with certificates that use the same public key...
+  ctx->readerPublicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE;
+  if (!eicOpsX509GetPublicKey(certX509, certX509Size, ctx->readerPublicKey,
+                              &ctx->readerPublicKeySize)) {
+    eicDebug("Error extracting public key from certificate");
+    return false;
+  }
+  if (ctx->readerPublicKeySize == 0) {
+    eicDebug("Zero-length public key in certificate");
+    return false;
+  }
+
+  return true;
+}
+
+bool eicPresentationSetAuthToken(
+    EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId,
+    uint64_t authenticatorId, int hardwareAuthenticatorType, uint64_t timeStamp,
+    const uint8_t* mac, size_t macSize, uint64_t verificationTokenChallenge,
+    uint64_t verificationTokenTimestamp, int verificationTokenSecurityLevel,
+    const uint8_t* verificationTokenMac, size_t verificationTokenMacSize) {
+  // It doesn't make sense to accept any tokens if
+  // eicPresentationCreateAuthChallenge() was never called.
+  if (ctx->authChallenge == 0) {
+    eicDebug(
+        "Trying validate tokens when no auth-challenge was previously "
+        "generated");
+    return false;
+  }
+  // At least the verification-token must have the same challenge as what was
+  // generated.
+  if (verificationTokenChallenge != ctx->authChallenge) {
+    eicDebug(
+        "Challenge in verification token does not match the challenge "
+        "previously generated");
+    return false;
+  }
+  if (!eicOpsValidateAuthToken(
+          challenge, secureUserId, authenticatorId, hardwareAuthenticatorType,
+          timeStamp, mac, macSize, verificationTokenChallenge,
+          verificationTokenTimestamp, verificationTokenSecurityLevel,
+          verificationTokenMac, verificationTokenMacSize)) {
+    return false;
+  }
+  ctx->authTokenChallenge = challenge;
+  ctx->authTokenSecureUserId = secureUserId;
+  ctx->authTokenTimestamp = timeStamp;
+  ctx->verificationTokenTimestamp = verificationTokenTimestamp;
+  return true;
+}
+
+static bool checkUserAuth(EicPresentation* ctx, bool userAuthenticationRequired,
+                          int timeoutMillis, uint64_t secureUserId) {
+  if (!userAuthenticationRequired) {
+    return true;
+  }
+
+  if (secureUserId != ctx->authTokenSecureUserId) {
+    eicDebug("secureUserId in profile differs from userId in authToken");
+    return false;
+  }
+
+  // Only ACP with auth-on-every-presentation - those with timeout == 0 - need
+  // the challenge to match...
+  if (timeoutMillis == 0) {
+    if (ctx->authTokenChallenge != ctx->authChallenge) {
+      eicDebug("Challenge in authToken (%" PRIu64
+               ") doesn't match the challenge "
+               "that was created (%" PRIu64 ") for this session",
+               ctx->authTokenChallenge, ctx->authChallenge);
+      return false;
+    }
+  }
+
+  uint64_t now = ctx->verificationTokenTimestamp;
+  if (ctx->authTokenTimestamp > now) {
+    eicDebug("Timestamp in authToken is in the future");
+    return false;
+  }
+
+  if (timeoutMillis > 0) {
+    if (now > ctx->authTokenTimestamp + timeoutMillis) {
+      eicDebug("Deadline for authToken is in the past");
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static bool checkReaderAuth(EicPresentation* ctx,
+                            const uint8_t* readerCertificate,
+                            size_t readerCertificateSize) {
+  uint8_t publicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE];
+  size_t publicKeySize;
+
+  if (readerCertificateSize == 0) {
+    return true;
+  }
+
+  // Remember in this case certificate equality is done by comparing public
+  // keys, not bitwise comparison of the certificates.
+  //
+  publicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE;
+  if (!eicOpsX509GetPublicKey(readerCertificate, readerCertificateSize,
+                              publicKey, &publicKeySize)) {
+    eicDebug("Error extracting public key from certificate");
+    return false;
+  }
+  if (publicKeySize == 0) {
+    eicDebug("Zero-length public key in certificate");
+    return false;
+  }
+
+  if ((ctx->readerPublicKeySize != publicKeySize) ||
+      (eicCryptoMemCmp(ctx->readerPublicKey, publicKey,
+                       ctx->readerPublicKeySize) != 0)) {
+    return false;
+  }
+  return true;
+}
+
+// Note: This function returns false _only_ if an error occurred check for
+// access, _not_ whether access is granted. Whether access is granted is
+// returned in |accessGranted|.
+//
+bool eicPresentationValidateAccessControlProfile(
+    EicPresentation* ctx, int id, const uint8_t* readerCertificate,
+    size_t readerCertificateSize, bool userAuthenticationRequired,
+    int timeoutMillis, uint64_t secureUserId, const uint8_t mac[28],
+    bool* accessGranted, uint8_t* scratchSpace, size_t scratchSpaceSize) {
+  *accessGranted = false;
+  if (id < 0 || id >= 32) {
+    eicDebug("id value of %d is out of allowed range [0, 32[", id);
+    return false;
+  }
+
+  // Validate the MAC
+  EicCbor cborBuilder;
+  eicCborInit(&cborBuilder, scratchSpace, scratchSpaceSize);
+  if (!eicCborCalcAccessControl(
+          &cborBuilder, id, readerCertificate, readerCertificateSize,
+          userAuthenticationRequired, timeoutMillis, secureUserId)) {
+    return false;
+  }
+  if (!eicOpsDecryptAes128Gcm(ctx->storageKey, mac, 28, cborBuilder.buffer,
+                              cborBuilder.size, NULL)) {
+    eicDebug("MAC for AccessControlProfile doesn't match");
+    return false;
+  }
+
+  bool passedUserAuth = checkUserAuth(ctx, userAuthenticationRequired,
+                                      timeoutMillis, secureUserId);
+  bool passedReaderAuth =
+      checkReaderAuth(ctx, readerCertificate, readerCertificateSize);
+
+  ctx->accessControlProfileMaskValidated |= (1U << id);
+  if (readerCertificateSize > 0) {
+    ctx->accessControlProfileMaskUsesReaderAuth |= (1U << id);
+  }
+  if (!passedReaderAuth) {
+    ctx->accessControlProfileMaskFailedReaderAuth |= (1U << id);
+  }
+  if (!passedUserAuth) {
+    ctx->accessControlProfileMaskFailedUserAuth |= (1U << id);
+  }
+
+  if (passedUserAuth && passedReaderAuth) {
+    *accessGranted = true;
+    eicDebug("Access granted for id %d", id);
+  }
+  return true;
+}
+
+bool eicPresentationCalcMacKey(
+    EicPresentation* ctx, const uint8_t* sessionTranscript,
+    size_t sessionTranscriptSize,
+    const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
+    const uint8_t signingKeyBlob[60], const char* docType, size_t docTypeLength,
+    unsigned int numNamespacesWithValues, size_t expectedDeviceNamespacesSize) {
+  uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
+  if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60,
+                              (const uint8_t*)docType, docTypeLength,
+                              signingKeyPriv)) {
+    eicDebug("Error decrypting signingKeyBlob");
+    return false;
+  }
+
+  uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE];
+  if (!eicOpsEcdh(readerEphemeralPublicKey, signingKeyPriv, sharedSecret)) {
+    eicDebug("ECDH failed");
+    return false;
+  }
+
+  EicCbor cbor;
+  eicCborInit(&cbor, NULL, 0);
+  eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+  eicCborAppendByteString(&cbor, sessionTranscript, sessionTranscriptSize);
+  uint8_t salt[EIC_SHA256_DIGEST_SIZE];
+  eicCborFinal(&cbor, salt);
+
+  const uint8_t info[7] = {'E', 'M', 'a', 'c', 'K', 'e', 'y'};
+  uint8_t derivedKey[32];
+  if (!eicOpsHkdf(sharedSecret, EIC_P256_COORDINATE_SIZE, salt, sizeof(salt),
+                  info, sizeof(info), derivedKey, sizeof(derivedKey))) {
+    eicDebug("HKDF failed");
+    return false;
+  }
+
+  eicCborInitHmacSha256(&ctx->cbor, NULL, 0, derivedKey, sizeof(derivedKey));
+  ctx->buildCbor = true;
+
+  // What we're going to calculate the HMAC-SHA256 is the COSE ToBeMaced
+  // structure which looks like the following:
+  //
+  // MAC_structure = [
+  //   context : "MAC" / "MAC0",
+  //   protected : empty_or_serialized_map,
+  //   external_aad : bstr,
+  //   payload : bstr
+  // ]
+  //
+  eicCborAppendArray(&ctx->cbor, 4);
+  eicCborAppendStringZ(&ctx->cbor, "MAC0");
+
+  // The COSE Encoded protected headers is just a single field with
+  // COSE_LABEL_ALG (1) -> COSE_ALG_HMAC_256_256 (5). For simplicitly we just
+  // hard-code the CBOR encoding:
+  static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x05};
+  eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders,
+                          sizeof(coseEncodedProtectedHeaders));
+
+  // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+  // so external_aad is the empty bstr
+  static const uint8_t externalAad[0] = {};
+  eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad));
+
+  // For the payload, the _encoded_ form follows here. We handle this by simply
+  // opening a bstr, and then writing the CBOR. This requires us to know the
+  // size of said bstr, ahead of time... the CBOR to be written is
+  //
+  //   DeviceAuthentication = [
+  //      "DeviceAuthentication",
+  //      SessionTranscript,
+  //      DocType,                ; DocType as used in Documents structure in
+  //      OfflineResponse DeviceNameSpacesBytes
+  //   ]
+  //
+  //   DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+  //
+  //   DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication)
+  //
+  // which is easily calculated below
+  //
+  size_t calculatedSize = 0;
+  calculatedSize += 1;  // Array of size 4
+  calculatedSize += 1;  // "DeviceAuthentication" less than 24 bytes
+  calculatedSize +=
+      sizeof("DeviceAuthentication") - 1;   // Don't include trailing NUL
+  calculatedSize += sessionTranscriptSize;  // Already CBOR encoded
+  calculatedSize +=
+      1 + eicCborAdditionalLengthBytesFor(docTypeLength) + docTypeLength;
+  calculatedSize += 2;  // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+  calculatedSize +=
+      1 + eicCborAdditionalLengthBytesFor(expectedDeviceNamespacesSize);
+  calculatedSize += expectedDeviceNamespacesSize;
+
+  // However note that we're authenticating DeviceAuthenticationBytes which
+  // is a tagged bstr of the bytes of DeviceAuthentication. So need to get
+  // that in front.
+  size_t dabCalculatedSize = 0;
+  dabCalculatedSize +=
+      2;  // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+  dabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize);
+  dabCalculatedSize += calculatedSize;
+
+  // Begin the bytestring for DeviceAuthenticationBytes;
+  eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dabCalculatedSize);
+
+  eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+
+  // Begins the bytestring for DeviceAuthentication;
+  eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);
+
+  eicCborAppendArray(&ctx->cbor, 4);
+  eicCborAppendStringZ(&ctx->cbor, "DeviceAuthentication");
+  eicCborAppend(&ctx->cbor, sessionTranscript, sessionTranscriptSize);
+  eicCborAppendString(&ctx->cbor, docType, docTypeLength);
+
+  // For the payload, the _encoded_ form follows here. We handle this by simply
+  // opening a bstr, and then writing the CBOR. This requires us to know the
+  // size of said bstr, ahead of time.
+  eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+  eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING,
+               expectedDeviceNamespacesSize);
+  ctx->expectedCborSizeAtEnd = expectedDeviceNamespacesSize + ctx->cbor.size;
+
+  eicCborAppendMap(&ctx->cbor, numNamespacesWithValues);
+  return true;
+}
+
+bool eicPresentationStartRetrieveEntries(EicPresentation* ctx) {
+  // HAL may use this object multiple times to retrieve data so need to reset
+  // various state objects here.
+  ctx->requestMessageValidated = false;
+  ctx->buildCbor = false;
+  ctx->accessControlProfileMaskValidated = 0;
+  ctx->accessControlProfileMaskUsesReaderAuth = 0;
+  ctx->accessControlProfileMaskFailedReaderAuth = 0;
+  ctx->accessControlProfileMaskFailedUserAuth = 0;
+  ctx->readerPublicKeySize = 0;
+  return true;
+}
+
+EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
+    EicPresentation* ctx, const char* nameSpace, size_t nameSpaceLength,
+    const char* name, size_t nameLength, unsigned int newNamespaceNumEntries,
+    int32_t entrySize, const uint8_t* accessControlProfileIds,
+    size_t numAccessControlProfileIds, uint8_t* scratchSpace,
+    size_t scratchSpaceSize) {
+  (void)entrySize;
+  uint8_t* additionalDataCbor = scratchSpace;
+  size_t additionalDataCborBufferSize = scratchSpaceSize;
+  size_t additionalDataCborSize;
+
+  if (newNamespaceNumEntries > 0) {
+    eicCborAppendString(&ctx->cbor, nameSpace, nameSpaceLength);
+    eicCborAppendMap(&ctx->cbor, newNamespaceNumEntries);
+  }
+
+  // We'll need to calc and store a digest of additionalData to check that it's
+  // the same additionalData being passed in for every
+  // eicPresentationRetrieveEntryValue() call...
+  //
+  ctx->accessCheckOk = false;
+  if (!eicCborCalcEntryAdditionalData(
+          accessControlProfileIds, numAccessControlProfileIds, nameSpace,
+          nameSpaceLength, name, nameLength, additionalDataCbor,
+          additionalDataCborBufferSize, &additionalDataCborSize,
+          ctx->additionalDataSha256)) {
+    return EIC_ACCESS_CHECK_RESULT_FAILED;
+  }
+
+  if (numAccessControlProfileIds == 0) {
+    return EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES;
+  }
+
+  // Access is granted if at least one of the profiles grants access.
+  //
+  // If an item is configured without any profiles, access is denied.
+  //
+  EicAccessCheckResult result = EIC_ACCESS_CHECK_RESULT_FAILED;
+  for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+    int id = accessControlProfileIds[n];
+    uint32_t idBitMask = (1 << id);
+
+    // If the access control profile wasn't validated, this is an error and we
+    // fail immediately.
+    bool validated =
+        ((ctx->accessControlProfileMaskValidated & idBitMask) != 0);
+    if (!validated) {
+      eicDebug("No ACP for profile id %d", id);
+      return EIC_ACCESS_CHECK_RESULT_FAILED;
+    }
+
+    // Otherwise, we _did_ validate the profile. If none of the checks
+    // failed, we're done
+    bool failedUserAuth =
+        ((ctx->accessControlProfileMaskFailedUserAuth & idBitMask) != 0);
+    bool failedReaderAuth =
+        ((ctx->accessControlProfileMaskFailedReaderAuth & idBitMask) != 0);
+    if (!failedUserAuth && !failedReaderAuth) {
+      result = EIC_ACCESS_CHECK_RESULT_OK;
+      break;
+    }
+    // One of the checks failed, convey which one
+    if (failedUserAuth) {
+      result = EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED;
+    } else {
+      result = EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED;
+    }
+  }
+  eicDebug("Result %d for name %s", result, name);
+
+  if (result == EIC_ACCESS_CHECK_RESULT_OK) {
+    eicCborAppendString(&ctx->cbor, name, nameLength);
+    ctx->accessCheckOk = true;
+  }
+  return result;
+}
+
+// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes.
+bool eicPresentationRetrieveEntryValue(
+    EicPresentation* ctx, const uint8_t* encryptedContent,
+    size_t encryptedContentSize, uint8_t* content, const char* nameSpace,
+    size_t nameSpaceLength, const char* name, size_t nameLength,
+    const uint8_t* accessControlProfileIds, size_t numAccessControlProfileIds,
+    uint8_t* scratchSpace, size_t scratchSpaceSize) {
+  uint8_t* additionalDataCbor = scratchSpace;
+  size_t additionalDataCborBufferSize = scratchSpaceSize;
+  size_t additionalDataCborSize;
+
+  uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE];
+  if (!eicCborCalcEntryAdditionalData(
+          accessControlProfileIds, numAccessControlProfileIds, nameSpace,
+          nameSpaceLength, name, nameLength, additionalDataCbor,
+          additionalDataCborBufferSize, &additionalDataCborSize,
+          calculatedSha256)) {
+    return false;
+  }
+
+  if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256,
+                      EIC_SHA256_DIGEST_SIZE) != 0) {
+    eicDebug("SHA-256 mismatch of additionalData");
+    return false;
+  }
+  if (!ctx->accessCheckOk) {
+    eicDebug("Attempting to retrieve a value for which access is not granted");
+    return false;
+  }
+
+  if (!eicOpsDecryptAes128Gcm(ctx->storageKey, encryptedContent,
+                              encryptedContentSize, additionalDataCbor,
+                              additionalDataCborSize, content)) {
+    eicDebug("Error decrypting content");
+    return false;
+  }
+
+  eicCborAppend(&ctx->cbor, content, encryptedContentSize - 28);
+
+  return true;
+}
+
+bool eicPresentationFinishRetrieval(EicPresentation* ctx,
+                                    uint8_t* digestToBeMaced,
+                                    size_t* digestToBeMacedSize) {
+  if (!ctx->buildCbor) {
+    *digestToBeMacedSize = 0;
+    return true;
+  }
+  if (*digestToBeMacedSize != 32) {
+    return false;
+  }
+
+  // This verifies that the correct expectedDeviceNamespacesSize value was
+  // passed in at eicPresentationCalcMacKey() time.
+  if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) {
+    eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size,
+             ctx->expectedCborSizeAtEnd);
+    return false;
+  }
+  eicCborFinal(&ctx->cbor, digestToBeMaced);
+  return true;
+}
+
+bool eicPresentationDeleteCredential(
+    EicPresentation* ctx, const char* docType, size_t docTypeLength,
+    const uint8_t* challenge, size_t challengeSize, bool includeChallenge,
+    size_t proofOfDeletionCborSize,
+    uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+  EicCbor cbor;
+
+  eicCborInit(&cbor, NULL, 0);
+
+  // What we're going to sign is the COSE ToBeSigned structure which
+  // looks like the following:
+  //
+  // Sig_structure = [
+  //   context : "Signature" / "Signature1" / "CounterSignature",
+  //   body_protected : empty_or_serialized_map,
+  //   ? sign_protected : empty_or_serialized_map,
+  //   external_aad : bstr,
+  //   payload : bstr
+  //  ]
+  //
+  eicCborAppendArray(&cbor, 4);
+  eicCborAppendStringZ(&cbor, "Signature1");
+
+  // The COSE Encoded protected headers is just a single field with
+  // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+  // hard-code the CBOR encoding:
+  static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+  eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+                          sizeof(coseEncodedProtectedHeaders));
+
+  // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+  // so external_aad is the empty bstr
+  static const uint8_t externalAad[0] = {};
+  eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+  // For the payload, the _encoded_ form follows here. We handle this by simply
+  // opening a bstr, and then writing the CBOR. This requires us to know the
+  // size of said bstr, ahead of time.
+  eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, proofOfDeletionCborSize);
+
+  // Finally, the CBOR that we're actually signing.
+  eicCborAppendArray(&cbor, includeChallenge ? 4 : 3);
+  eicCborAppendStringZ(&cbor, "ProofOfDeletion");
+  eicCborAppendString(&cbor, docType, docTypeLength);
+  if (includeChallenge) {
+    eicCborAppendByteString(&cbor, challenge, challengeSize);
+  }
+  eicCborAppendBool(&cbor, ctx->testCredential);
+
+  uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+  eicCborFinal(&cbor, cborSha256);
+  if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256,
+                   signatureOfToBeSigned)) {
+    eicDebug("Error signing proofOfDeletion");
+    return false;
+  }
+
+  return true;
+}
+
+bool eicPresentationProveOwnership(
+    EicPresentation* ctx, const char* docType, size_t docTypeLength,
+    bool testCredential, const uint8_t* challenge, size_t challengeSize,
+    size_t proofOfOwnershipCborSize,
+    uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+  EicCbor cbor;
+
+  eicCborInit(&cbor, NULL, 0);
+
+  // What we're going to sign is the COSE ToBeSigned structure which
+  // looks like the following:
+  //
+  // Sig_structure = [
+  //   context : "Signature" / "Signature1" / "CounterSignature",
+  //   body_protected : empty_or_serialized_map,
+  //   ? sign_protected : empty_or_serialized_map,
+  //   external_aad : bstr,
+  //   payload : bstr
+  //  ]
+  //
+  eicCborAppendArray(&cbor, 4);
+  eicCborAppendStringZ(&cbor, "Signature1");
+
+  // The COSE Encoded protected headers is just a single field with
+  // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+  // hard-code the CBOR encoding:
+  static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+  eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+                          sizeof(coseEncodedProtectedHeaders));
+
+  // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+  // so external_aad is the empty bstr
+  static const uint8_t externalAad[0] = {};
+  eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+  // For the payload, the _encoded_ form follows here. We handle this by simply
+  // opening a bstr, and then writing the CBOR. This requires us to know the
+  // size of said bstr, ahead of time.
+  eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING,
+               proofOfOwnershipCborSize);
+
+  // Finally, the CBOR that we're actually signing.
+  eicCborAppendArray(&cbor, 4);
+  eicCborAppendStringZ(&cbor, "ProofOfOwnership");
+  eicCborAppendString(&cbor, docType, docTypeLength);
+  eicCborAppendByteString(&cbor, challenge, challengeSize);
+  eicCborAppendBool(&cbor, testCredential);
+
+  uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+  eicCborFinal(&cbor, cborSha256);
+  if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256,
+                   signatureOfToBeSigned)) {
+    eicDebug("Error signing proofOfDeletion");
+    return false;
+  }
+
+  return true;
+}
diff --git a/guest/hals/identity/libeic/EicPresentation.h b/guest/hals/identity/libeic/EicPresentation.h
new file mode 100644
index 0000000..72d8401
--- /dev/null
+++ b/guest/hals/identity/libeic/EicPresentation.h
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicCbor.h"
+
+// The maximum size we support for public keys in reader certificates.
+#define EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE 65
+
+typedef struct {
+  int featureLevel;
+
+  uint8_t storageKey[EIC_AES_128_KEY_SIZE];
+  uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+  uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+  // The challenge generated with eicPresentationCreateAuthChallenge()
+  uint64_t authChallenge;
+
+  // Set by eicPresentationSetAuthToken() and contains the fields
+  // from the passed in authToken and verificationToken.
+  //
+  uint64_t authTokenChallenge;
+  uint64_t authTokenSecureUserId;
+  uint64_t authTokenTimestamp;
+  uint64_t verificationTokenTimestamp;
+
+  // The public key for the reader.
+  //
+  // (During the process of pushing reader certificates, this is also used to
+  // store the public key of the previously pushed certificate.)
+  //
+  uint8_t readerPublicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE];
+  size_t readerPublicKeySize;
+
+  // This is set to true only if eicPresentationValidateRequestMessage()
+  // successfully validated the requestMessage.
+  //
+  // Why even record this? Because there's no requirement the HAL actually calls
+  // that function and we validate ACPs before it's called... so it's possible
+  // that a compromised HAL could trick us into marking ACPs as authorized while
+  // they in fact aren't.
+  bool requestMessageValidated;
+  bool buildCbor;
+
+  // Set to true initialized as a test credential.
+  bool testCredential;
+
+  // Set to true if the evaluation of access control checks in
+  // eicPresentationStartRetrieveEntryValue() resulted
+  // EIC_ACCESS_CHECK_RESULT_OK
+  bool accessCheckOk;
+
+  // These are bitmasks indicating which of the possible 32 access control
+  // profiles are authorized. They are built up by
+  // eicPresentationValidateAccessControlProfile().
+  //
+  uint32_t
+      accessControlProfileMaskValidated;  // True if the profile was validated.
+  uint32_t accessControlProfileMaskUsesReaderAuth;  // True if the ACP is using
+                                                    // reader auth
+  uint32_t
+      accessControlProfileMaskFailedReaderAuth;  // True if failed reader auth
+  uint32_t accessControlProfileMaskFailedUserAuth;  // True if failed user auth
+
+  // SHA-256 for AdditionalData, updated for each entry.
+  uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE];
+
+  // SHA-256 of ProofOfProvisioning. Set to NUL-bytes or initialized from
+  // CredentialKeys data if credential was created with feature version 202101
+  // or later.
+  uint8_t proofOfProvisioningSha256[EIC_SHA256_DIGEST_SIZE];
+
+  size_t expectedCborSizeAtEnd;
+  EicCbor cbor;
+} EicPresentation;
+
+bool eicPresentationInit(EicPresentation* ctx, bool testCredential,
+                         const char* docType, size_t docTypeLength,
+                         const uint8_t* encryptedCredentialKeys,
+                         size_t encryptedCredentialKeysSize);
+
+bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx,
+                                           const char* docType,
+                                           size_t docTypeLength, time_t now,
+                                           uint8_t* publicKeyCert,
+                                           size_t* publicKeyCertSize,
+                                           uint8_t signingKeyBlob[60]);
+
+// Create an ephemeral key-pair.
+//
+// The private key is stored in |ctx->ephemeralPrivateKey| and also returned in
+// |ephemeralPrivateKey|.
+//
+bool eicPresentationCreateEphemeralKeyPair(
+    EicPresentation* ctx, uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]);
+
+// Returns a non-zero challenge in |authChallenge|.
+bool eicPresentationCreateAuthChallenge(EicPresentation* ctx,
+                                        uint64_t* authChallenge);
+
+// Starts retrieving entries.
+//
+bool eicPresentationStartRetrieveEntries(EicPresentation* ctx);
+
+// Sets the auth-token.
+bool eicPresentationSetAuthToken(
+    EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId,
+    uint64_t authenticatorId, int hardwareAuthenticatorType, uint64_t timeStamp,
+    const uint8_t* mac, size_t macSize, uint64_t verificationTokenChallenge,
+    uint64_t verificationTokenTimeStamp, int verificationTokenSecurityLevel,
+    const uint8_t* verificationTokenMac, size_t verificationTokenMacSize);
+
+// Function to push certificates in the reader certificate chain.
+//
+// This should start with the root certificate (e.g. the last in the chain) and
+// continue up the chain, ending with the certificate for the reader.
+//
+// Calls to this function should be interleaved with calls to the
+// eicPresentationValidateAccessControlProfile() function, see below.
+//
+bool eicPresentationPushReaderCert(EicPresentation* ctx,
+                                   const uint8_t* certX509,
+                                   size_t certX509Size);
+
+// Checks an access control profile.
+//
+// Returns false if an error occurred while checking the profile (e.g. MAC
+// doesn't check out).
+//
+// Returns in |accessGranted| whether access is granted.
+//
+// If |readerCertificate| is non-empty and the public key of one of those
+// certificates appear in the chain presented by the reader, this function must
+// be called after pushing that certificate using
+// eicPresentationPushReaderCert().
+//
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done
+// this way to avoid allocating stack space.
+//
+bool eicPresentationValidateAccessControlProfile(
+    EicPresentation* ctx, int id, const uint8_t* readerCertificate,
+    size_t readerCertificateSize, bool userAuthenticationRequired,
+    int timeoutMillis, uint64_t secureUserId, const uint8_t mac[28],
+    bool* accessGranted, uint8_t* scratchSpace, size_t scratchSpaceSize);
+
+// Validates that the given requestMessage is signed by the public key in the
+// certificate last set with eicPresentationPushReaderCert().
+//
+// The format of the signature is the same encoding as the 'signature' field of
+// COSE_Sign1 - that is, it's the R and S integers both with the same length as
+// the key-size.
+//
+// Must be called after eicPresentationPushReaderCert() have been used to push
+// the final certificate. Which is the certificate of the reader itself.
+//
+bool eicPresentationValidateRequestMessage(
+    EicPresentation* ctx, const uint8_t* sessionTranscript,
+    size_t sessionTranscriptSize, const uint8_t* requestMessage,
+    size_t requestMessageSize, int coseSignAlg,
+    const uint8_t* readerSignatureOfToBeSigned,
+    size_t readerSignatureOfToBeSignedSize);
+
+typedef enum {
+  // Returned if access is granted.
+  EIC_ACCESS_CHECK_RESULT_OK,
+
+  // Returned if an error occurred checking for access.
+  EIC_ACCESS_CHECK_RESULT_FAILED,
+
+  // Returned if access was denied because item is configured without any
+  // access control profiles.
+  EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES,
+
+  // Returned if access was denied because of user authentication.
+  EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED,
+
+  // Returned if access was denied because of reader authentication.
+  EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED,
+} EicAccessCheckResult;
+
+// Passes enough information to calculate the MACing key
+//
+bool eicPresentationCalcMacKey(
+    EicPresentation* ctx, const uint8_t* sessionTranscript,
+    size_t sessionTranscriptSize,
+    const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
+    const uint8_t signingKeyBlob[60], const char* docType, size_t docTypeLength,
+    unsigned int numNamespacesWithValues, size_t expectedDeviceNamespacesSize);
+
+// The scratchSpace should be set to a buffer at least 512 bytes (ideally 1024
+// bytes, the bigger the better). It's done this way to avoid allocating stack
+// space.
+//
+EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
+    EicPresentation* ctx, const char* nameSpace, size_t nameSpaceLength,
+    const char* name, size_t nameLength, unsigned int newNamespaceNumEntries,
+    int32_t entrySize, const uint8_t* accessControlProfileIds,
+    size_t numAccessControlProfileIds, uint8_t* scratchSpace,
+    size_t scratchSpaceSize);
+
+// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes.
+//
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this
+// way to avoid allocating stack space.
+//
+bool eicPresentationRetrieveEntryValue(
+    EicPresentation* ctx, const uint8_t* encryptedContent,
+    size_t encryptedContentSize, uint8_t* content, const char* nameSpace,
+    size_t nameSpaceLength, const char* name, size_t nameLength,
+    const uint8_t* accessControlProfileIds, size_t numAccessControlProfileIds,
+    uint8_t* scratchSpace, size_t scratchSpaceSize);
+
+// Returns the HMAC-SHA256 of |ToBeMaced| as per RFC 8051 "6.3. How to Compute
+// and Verify a MAC".
+bool eicPresentationFinishRetrieval(EicPresentation* ctx,
+                                    uint8_t* digestToBeMaced,
+                                    size_t* digestToBeMacedSize);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfDeletion CBOR.
+//
+bool eicPresentationDeleteCredential(
+    EicPresentation* ctx, const char* docType, size_t docTypeLength,
+    const uint8_t* challenge, size_t challengeSize, bool includeChallenge,
+    size_t proofOfDeletionCborSize,
+    uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfOwnership CBOR.
+//
+bool eicPresentationProveOwnership(
+    EicPresentation* ctx, const char* docType, size_t docTypeLength,
+    bool testCredential, const uint8_t* challenge, size_t challengeSize,
+    size_t proofOfOwnershipCborSize,
+    uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
diff --git a/guest/hals/identity/libeic/EicProvisioning.c b/guest/hals/identity/libeic/EicProvisioning.c
new file mode 100644
index 0000000..1f691cd
--- /dev/null
+++ b/guest/hals/identity/libeic/EicProvisioning.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EicProvisioning.h"
+#include "EicCommon.h"
+
+bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential) {
+  eicMemSet(ctx, '\0', sizeof(EicProvisioning));
+  ctx->testCredential = testCredential;
+  if (!eicOpsRandom(ctx->storageKey, EIC_AES_128_KEY_SIZE)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential,
+                                  const char* docType, size_t docTypeLength,
+                                  const uint8_t* encryptedCredentialKeys,
+                                  size_t encryptedCredentialKeysSize) {
+  uint8_t credentialKeys[EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101];
+
+  // For feature version 202009 it's 52 bytes long and for feature version
+  // 202101 it's 86 bytes (the additional data is the ProofOfProvisioning
+  // SHA-256). We need to support loading all feature versions.
+  //
+  bool expectPopSha256 = false;
+  if (encryptedCredentialKeysSize ==
+      EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202009 + 28) {
+    /* do nothing */
+  } else if (encryptedCredentialKeysSize ==
+             EIC_CREDENTIAL_KEYS_CBOR_SIZE_FEATURE_VERSION_202101 + 28) {
+    expectPopSha256 = true;
+  } else {
+    eicDebug("Unexpected size %zd for encryptedCredentialKeys",
+             encryptedCredentialKeysSize);
+    return false;
+  }
+
+  eicMemSet(ctx, '\0', sizeof(EicProvisioning));
+  ctx->testCredential = testCredential;
+
+  if (!eicOpsDecryptAes128Gcm(
+          eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
+          encryptedCredentialKeysSize,
+          // DocType is the additionalAuthenticatedData
+          (const uint8_t*)docType, docTypeLength, credentialKeys)) {
+    eicDebug("Error decrypting CredentialKeys");
+    return false;
+  }
+
+  // It's supposed to look like this;
+  //
+  // Feature version 202009:
+  //
+  //         CredentialKeys = [
+  //              bstr,   ; storageKey, a 128-bit AES key
+  //              bstr,   ; credentialPrivKey, the private key for credentialKey
+  //         ]
+  //
+  // Feature version 202101:
+  //
+  //         CredentialKeys = [
+  //              bstr,   ; storageKey, a 128-bit AES key
+  //              bstr,   ; credentialPrivKey, the private key for credentialKey
+  //              bstr    ; proofOfProvisioning SHA-256
+  //         ]
+  //
+  // where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and
+  // proofOfProvisioning SHA-256 is 32 bytes.
+  //
+  if (credentialKeys[0] !=
+          (expectPopSha256 ? 0x83 : 0x82) ||  // array of two or three elements
+      credentialKeys[1] != 0x50 ||            // 16-byte bstr
+      credentialKeys[18] != 0x58 ||
+      credentialKeys[19] != 0x20) {  // 32-byte bstr
+    eicDebug("Invalid CBOR for CredentialKeys");
+    return false;
+  }
+  if (expectPopSha256) {
+    if (credentialKeys[52] != 0x58 ||
+        credentialKeys[53] != 0x20) {  // 32-byte bstr
+      eicDebug("Invalid CBOR for CredentialKeys");
+      return false;
+    }
+  }
+  eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE);
+  eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20,
+            EIC_P256_PRIV_KEY_SIZE);
+  // Note: We don't care about the previous ProofOfProvisioning SHA-256
+  ctx->isUpdate = true;
+  return true;
+}
+
+bool eicProvisioningCreateCredentialKey(
+    EicProvisioning* ctx, const uint8_t* challenge, size_t challengeSize,
+    const uint8_t* applicationId, size_t applicationIdSize,
+    uint8_t* publicKeyCert, size_t* publicKeyCertSize) {
+  if (ctx->isUpdate) {
+    eicDebug("Cannot create CredentialKey on update");
+    return false;
+  }
+
+  if (!eicOpsCreateCredentialKey(ctx->credentialPrivateKey, challenge,
+                                 challengeSize, applicationId,
+                                 applicationIdSize, ctx->testCredential,
+                                 publicKeyCert, publicKeyCertSize)) {
+    return false;
+  }
+  return true;
+}
+
+bool eicProvisioningStartPersonalization(
+    EicProvisioning* ctx, int accessControlProfileCount, const int* entryCounts,
+    size_t numEntryCounts, const char* docType, size_t docTypeLength,
+    size_t expectedProofOfProvisioningSize) {
+  if (numEntryCounts >= EIC_MAX_NUM_NAMESPACES) {
+    return false;
+  }
+  if (accessControlProfileCount >= EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS) {
+    return false;
+  }
+
+  ctx->numEntryCounts = numEntryCounts;
+  if (numEntryCounts > EIC_MAX_NUM_NAMESPACES) {
+    return false;
+  }
+  for (size_t n = 0; n < numEntryCounts; n++) {
+    if (entryCounts[n] >= 256) {
+      return false;
+    }
+    ctx->entryCounts[n] = entryCounts[n];
+  }
+  ctx->curNamespace = -1;
+  ctx->curNamespaceNumProcessed = 0;
+
+  eicCborInit(&ctx->cbor, NULL, 0);
+
+  // What we're going to sign is the COSE ToBeSigned structure which
+  // looks like the following:
+  //
+  // Sig_structure = [
+  //   context : "Signature" / "Signature1" / "CounterSignature",
+  //   body_protected : empty_or_serialized_map,
+  //   ? sign_protected : empty_or_serialized_map,
+  //   external_aad : bstr,
+  //   payload : bstr
+  //  ]
+  //
+  eicCborAppendArray(&ctx->cbor, 4);
+  eicCborAppendStringZ(&ctx->cbor, "Signature1");
+
+  // The COSE Encoded protected headers is just a single field with
+  // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+  // hard-code the CBOR encoding:
+  static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+  eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders,
+                          sizeof(coseEncodedProtectedHeaders));
+
+  // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+  // so external_aad is the empty bstr
+  static const uint8_t externalAad[0] = {};
+  eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad));
+
+  // For the payload, the _encoded_ form follows here. We handle this by simply
+  // opening a bstr, and then writing the CBOR. This requires us to know the
+  // size of said bstr, ahead of time.
+  eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING,
+               expectedProofOfProvisioningSize);
+  ctx->expectedCborSizeAtEnd = expectedProofOfProvisioningSize + ctx->cbor.size;
+
+  eicOpsSha256Init(&ctx->proofOfProvisioningDigester);
+  eicCborEnableSecondaryDigesterSha256(&ctx->cbor,
+                                       &ctx->proofOfProvisioningDigester);
+
+  eicCborAppendArray(&ctx->cbor, 5);
+  eicCborAppendStringZ(&ctx->cbor, "ProofOfProvisioning");
+  eicCborAppendString(&ctx->cbor, docType, docTypeLength);
+
+  eicCborAppendArray(&ctx->cbor, accessControlProfileCount);
+
+  return true;
+}
+
+bool eicProvisioningAddAccessControlProfile(
+    EicProvisioning* ctx, int id, const uint8_t* readerCertificate,
+    size_t readerCertificateSize, bool userAuthenticationRequired,
+    uint64_t timeoutMillis, uint64_t secureUserId, uint8_t outMac[28],
+    uint8_t* scratchSpace, size_t scratchSpaceSize) {
+  EicCbor cborBuilder;
+  eicCborInit(&cborBuilder, scratchSpace, scratchSpaceSize);
+
+  if (!eicCborCalcAccessControl(
+          &cborBuilder, id, readerCertificate, readerCertificateSize,
+          userAuthenticationRequired, timeoutMillis, secureUserId)) {
+    return false;
+  }
+
+  // Calculate and return MAC
+  uint8_t nonce[12];
+  if (!eicOpsRandom(nonce, 12)) {
+    return false;
+  }
+  if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, NULL, 0,
+                              cborBuilder.buffer, cborBuilder.size, outMac)) {
+    return false;
+  }
+
+  // The ACP CBOR in the provisioning receipt doesn't include secureUserId so
+  // build it again.
+  eicCborInit(&cborBuilder, scratchSpace, scratchSpaceSize);
+  if (!eicCborCalcAccessControl(
+          &cborBuilder, id, readerCertificate, readerCertificateSize,
+          userAuthenticationRequired, timeoutMillis, 0 /* secureUserId */)) {
+    return false;
+  }
+
+  // Append the CBOR from the local builder to the digester.
+  eicCborAppend(&ctx->cbor, cborBuilder.buffer, cborBuilder.size);
+
+  return true;
+}
+
+bool eicProvisioningBeginAddEntry(EicProvisioning* ctx,
+                                  const uint8_t* accessControlProfileIds,
+                                  size_t numAccessControlProfileIds,
+                                  const char* nameSpace, size_t nameSpaceLength,
+                                  const char* name, size_t nameLength,
+                                  uint64_t entrySize, uint8_t* scratchSpace,
+                                  size_t scratchSpaceSize) {
+  uint8_t* additionalDataCbor = scratchSpace;
+  const size_t additionalDataCborBufSize = scratchSpaceSize;
+  size_t additionalDataCborSize;
+
+  // We'll need to calc and store a digest of additionalData to check that it's
+  // the same additionalData being passed in for every
+  // eicProvisioningAddEntryValue() call...
+  if (!eicCborCalcEntryAdditionalData(
+          accessControlProfileIds, numAccessControlProfileIds, nameSpace,
+          nameSpaceLength, name, nameLength, additionalDataCbor,
+          additionalDataCborBufSize, &additionalDataCborSize,
+          ctx->additionalDataSha256)) {
+    return false;
+  }
+
+  if (ctx->curNamespace == -1) {
+    ctx->curNamespace = 0;
+    ctx->curNamespaceNumProcessed = 0;
+    // Opens the main map: { * Namespace => [ + Entry ] }
+    eicCborAppendMap(&ctx->cbor, ctx->numEntryCounts);
+    eicCborAppendString(&ctx->cbor, nameSpace, nameSpaceLength);
+    // Opens the per-namespace array: [ + Entry ]
+    eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]);
+  }
+
+  if (ctx->curNamespaceNumProcessed == ctx->entryCounts[ctx->curNamespace]) {
+    ctx->curNamespace += 1;
+    ctx->curNamespaceNumProcessed = 0;
+    eicCborAppendString(&ctx->cbor, nameSpace, nameSpaceLength);
+    // Opens the per-namespace array: [ + Entry ]
+    eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]);
+  }
+
+  eicCborAppendMap(&ctx->cbor, 3);
+  eicCborAppendStringZ(&ctx->cbor, "name");
+  eicCborAppendString(&ctx->cbor, name, nameLength);
+
+  ctx->curEntrySize = entrySize;
+  ctx->curEntryNumBytesReceived = 0;
+
+  eicCborAppendStringZ(&ctx->cbor, "value");
+
+  ctx->curNamespaceNumProcessed += 1;
+  return true;
+}
+
+bool eicProvisioningAddEntryValue(
+    EicProvisioning* ctx, const uint8_t* accessControlProfileIds,
+    size_t numAccessControlProfileIds, const char* nameSpace,
+    size_t nameSpaceLength, const char* name, size_t nameLength,
+    const uint8_t* content, size_t contentSize, uint8_t* outEncryptedContent,
+    uint8_t* scratchSpace, size_t scratchSpaceSize) {
+  uint8_t* additionalDataCbor = scratchSpace;
+  const size_t additionalDataCborBufSize = scratchSpaceSize;
+  size_t additionalDataCborSize;
+  uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE];
+
+  if (!eicCborCalcEntryAdditionalData(
+          accessControlProfileIds, numAccessControlProfileIds, nameSpace,
+          nameSpaceLength, name, nameLength, additionalDataCbor,
+          additionalDataCborBufSize, &additionalDataCborSize,
+          calculatedSha256)) {
+    return false;
+  }
+  if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256,
+                      EIC_SHA256_DIGEST_SIZE) != 0) {
+    eicDebug("SHA-256 mismatch of additionalData");
+    return false;
+  }
+
+  eicCborAppend(&ctx->cbor, content, contentSize);
+
+  uint8_t nonce[12];
+  if (!eicOpsRandom(nonce, 12)) {
+    return false;
+  }
+  if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, content, contentSize,
+                              additionalDataCbor, additionalDataCborSize,
+                              outEncryptedContent)) {
+    return false;
+  }
+
+  // If done with this entry, close the map
+  ctx->curEntryNumBytesReceived += contentSize;
+  if (ctx->curEntryNumBytesReceived == ctx->curEntrySize) {
+    eicCborAppendStringZ(&ctx->cbor, "accessControlProfiles");
+    eicCborAppendArray(&ctx->cbor, numAccessControlProfileIds);
+    for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+      eicCborAppendNumber(&ctx->cbor, accessControlProfileIds[n]);
+    }
+  }
+  return true;
+}
+
+bool eicProvisioningFinishAddingEntries(
+    EicProvisioning* ctx,
+    uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+  uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+
+  eicCborAppendBool(&ctx->cbor, ctx->testCredential);
+  eicCborFinal(&ctx->cbor, cborSha256);
+
+  // This verifies that the correct expectedProofOfProvisioningSize value was
+  // passed in at eicStartPersonalization() time.
+  if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) {
+    eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size,
+             ctx->expectedCborSizeAtEnd);
+    return false;
+  }
+
+  if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256,
+                   signatureOfToBeSigned)) {
+    eicDebug("Error signing proofOfProvisioning");
+    return false;
+  }
+
+  return true;
+}
+
+bool eicProvisioningFinishGetCredentialData(
+    EicProvisioning* ctx, const char* docType, size_t docTypeLength,
+    uint8_t* encryptedCredentialKeys, size_t* encryptedCredentialKeysSize) {
+  EicCbor cbor;
+  uint8_t cborBuf[86];
+
+  if (*encryptedCredentialKeysSize < 86 + 28) {
+    eicDebug("encryptedCredentialKeysSize is %zd which is insufficient");
+    return false;
+  }
+
+  eicCborInit(&cbor, cborBuf, sizeof(cborBuf));
+  eicCborAppendArray(&cbor, 3);
+  eicCborAppendByteString(&cbor, ctx->storageKey, EIC_AES_128_KEY_SIZE);
+  eicCborAppendByteString(&cbor, ctx->credentialPrivateKey,
+                          EIC_P256_PRIV_KEY_SIZE);
+  uint8_t popSha256[EIC_SHA256_DIGEST_SIZE];
+  eicOpsSha256Final(&ctx->proofOfProvisioningDigester, popSha256);
+  eicCborAppendByteString(&cbor, popSha256, EIC_SHA256_DIGEST_SIZE);
+  if (cbor.size > sizeof(cborBuf)) {
+    eicDebug("Exceeded buffer size");
+    return false;
+  }
+
+  uint8_t nonce[12];
+  if (!eicOpsRandom(nonce, 12)) {
+    eicDebug("Error getting random");
+    return false;
+  }
+  if (!eicOpsEncryptAes128Gcm(eicOpsGetHardwareBoundKey(ctx->testCredential),
+                              nonce, cborBuf, cbor.size,
+                              // DocType is the additionalAuthenticatedData
+                              (const uint8_t*)docType, docTypeLength,
+                              encryptedCredentialKeys)) {
+    eicDebug("Error encrypting CredentialKeys");
+    return false;
+  }
+  *encryptedCredentialKeysSize = cbor.size + 28;
+
+  return true;
+}
diff --git a/guest/hals/identity/libeic/EicProvisioning.h b/guest/hals/identity/libeic/EicProvisioning.h
new file mode 100644
index 0000000..245a6e6
--- /dev/null
+++ b/guest/hals/identity/libeic/EicProvisioning.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicCbor.h"
+
+#define EIC_MAX_NUM_NAMESPACES 32
+#define EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS 32
+
+typedef struct {
+  // Set by eicCreateCredentialKey() OR eicProvisioningInitForUpdate()
+  uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+  int numEntryCounts;
+  uint8_t entryCounts[EIC_MAX_NUM_NAMESPACES];
+
+  int curNamespace;
+  int curNamespaceNumProcessed;
+
+  size_t curEntrySize;
+  size_t curEntryNumBytesReceived;
+
+  // Set by eicProvisioningInit() OR eicProvisioningInitForUpdate()
+  uint8_t storageKey[EIC_AES_128_KEY_SIZE];
+
+  size_t expectedCborSizeAtEnd;
+
+  // SHA-256 for AdditionalData, updated for each entry.
+  uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE];
+
+  // Digester just for ProofOfProvisioning (without Sig_structure).
+  EicSha256Ctx proofOfProvisioningDigester;
+
+  EicCbor cbor;
+
+  bool testCredential;
+
+  // Set to true if this is an update.
+  bool isUpdate;
+} EicProvisioning;
+
+bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential);
+
+bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential,
+                                  const char* docType, size_t docTypeLength,
+                                  const uint8_t* encryptedCredentialKeys,
+                                  size_t encryptedCredentialKeysSize);
+
+bool eicProvisioningCreateCredentialKey(
+    EicProvisioning* ctx, const uint8_t* challenge, size_t challengeSize,
+    const uint8_t* applicationId, size_t applicationIdSize,
+    uint8_t* publicKeyCert, size_t* publicKeyCertSize);
+
+bool eicProvisioningStartPersonalization(
+    EicProvisioning* ctx, int accessControlProfileCount, const int* entryCounts,
+    size_t numEntryCounts, const char* docType, size_t docTypeLength,
+    size_t expectedProofOfProvisioningingSize);
+
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this
+// way to avoid allocating stack space.
+//
+bool eicProvisioningAddAccessControlProfile(
+    EicProvisioning* ctx, int id, const uint8_t* readerCertificate,
+    size_t readerCertificateSize, bool userAuthenticationRequired,
+    uint64_t timeoutMillis, uint64_t secureUserId, uint8_t outMac[28],
+    uint8_t* scratchSpace, size_t scratchSpaceSize);
+
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this
+// way to avoid allocating stack space.
+//
+bool eicProvisioningBeginAddEntry(EicProvisioning* ctx,
+                                  const uint8_t* accessControlProfileIds,
+                                  size_t numAccessControlProfileIds,
+                                  const char* nameSpace, size_t nameSpaceLength,
+                                  const char* name, size_t nameLength,
+                                  uint64_t entrySize, uint8_t* scratchSpace,
+                                  size_t scratchSpaceSize);
+
+// The outEncryptedContent array must be contentSize + 28 bytes long.
+//
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this
+// way to avoid allocating stack space.
+//
+bool eicProvisioningAddEntryValue(
+    EicProvisioning* ctx, const uint8_t* accessControlProfileIds,
+    size_t numAccessControlProfileIds, const char* nameSpace,
+    size_t nameSpaceLength, const char* name, size_t nameLength,
+    const uint8_t* content, size_t contentSize, uint8_t* outEncryptedContent,
+    uint8_t* scratchSpace, size_t scratchSpaceSize);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfProvisioninging CBOR.
+//
+bool eicProvisioningFinishAddingEntries(
+    EicProvisioning* ctx,
+    uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+//
+//
+// The |encryptedCredentialKeys| array is set to AES-GCM-ENC(HBK, R,
+// CredentialKeys, docType) where
+//
+//   CredentialKeys = [
+//     bstr,   ; storageKey, a 128-bit AES key
+//     bstr    ; credentialPrivKey, the private key for credentialKey
+//     bstr    ; SHA-256(ProofOfProvisioning)
+//   ]
+//
+// for feature version 202101. For feature version 202009 the third field was
+// not present.
+//
+// Since |storageKey| is 16 bytes and |credentialPrivKey| is 32 bytes, the
+// encoded CBOR for CredentialKeys is 86 bytes and consequently
+// |encryptedCredentialKeys| will be no longer than 86 + 28 = 114 bytes.
+//
+bool eicProvisioningFinishGetCredentialData(
+    EicProvisioning* ctx, const char* docType, size_t docTypeLength,
+    uint8_t* encryptedCredentialKeys, size_t* encryptedCredentialKeysSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
diff --git a/guest/hals/identity/libeic/libeic.h b/guest/hals/identity/libeic/libeic.h
new file mode 100644
index 0000000..8674ce3
--- /dev/null
+++ b/guest/hals/identity/libeic/libeic.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_LIBEIC_H
+#define ANDROID_HARDWARE_IDENTITY_LIBEIC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The EIC_INSIDE_LIBEIC_H preprocessor symbol is used to enforce
+ * library users to include only this file. All public interfaces, and
+ * only public interfaces, must be included here.
+ */
+#define EIC_INSIDE_LIBEIC_H
+#include "EicCbor.h"
+#include "EicCommon.h"
+#include "EicOps.h"
+#include "EicPresentation.h"
+#include "EicProvisioning.h"
+#undef EIC_INSIDE_LIBEIC_H
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_HARDWARE_IDENTITY_LIBEIC_H
diff --git a/guest/hals/identity/service.cpp b/guest/hals/identity/service.cpp
new file mode 100644
index 0000000..b40dba4
--- /dev/null
+++ b/guest/hals/identity/service.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "android.hardware.identity-service"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "IdentityCredentialStore.h"
+
+#include "RemoteSecureHardwareProxy.h"
+
+using ::android::sp;
+using ::android::base::InitLogging;
+using ::android::base::StderrLogger;
+
+using ::aidl::android::hardware::identity::IdentityCredentialStore;
+using ::android::hardware::identity::RemoteSecureHardwareProxyFactory;
+using ::android::hardware::identity::SecureHardwareProxyFactory;
+
+int main(int /*argc*/, char* argv[]) {
+  InitLogging(argv, StderrLogger);
+
+  sp<SecureHardwareProxyFactory> hwProxyFactory =
+      new RemoteSecureHardwareProxyFactory();
+
+  ABinderProcess_setThreadPoolMaxThreadCount(0);
+  std::shared_ptr<IdentityCredentialStore> store =
+      ndk::SharedRefBase::make<IdentityCredentialStore>(hwProxyFactory);
+
+  const std::string instance =
+      std::string(IdentityCredentialStore::descriptor) + "/default";
+  binder_status_t status =
+      AServiceManager_addService(store->asBinder().get(), instance.c_str());
+  CHECK_EQ(status, STATUS_OK);
+
+  ABinderProcess_joinThreadPool();
+  return EXIT_FAILURE;  // should not reach
+}
diff --git a/guest/hals/keymaster/remote/remote_keymaster4_device.cpp b/guest/hals/keymaster/remote/remote_keymaster4_device.cpp
index b82aa83..dd0cd05 100644
--- a/guest/hals/keymaster/remote/remote_keymaster4_device.cpp
+++ b/guest/hals/keymaster/remote/remote_keymaster4_device.cpp
@@ -20,9 +20,10 @@
 #include "remote_keymaster4_device.h"
 
 #include <android/hardware/keymaster/3.0/IKeymasterDevice.h>
-#include <authorization_set.h>
 #include <cutils/log.h>
 #include <keymaster/android_keymaster_messages.h>
+#include <keymaster/authorization_set.h>
+#include <keymaster_tags.h>
 
 using ::keymaster::AbortOperationRequest;
 using ::keymaster::AbortOperationResponse;
diff --git a/guest/hals/keymint/remote/Android.bp b/guest/hals/keymint/remote/Android.bp
index a383c45..4944d37 100644
--- a/guest/hals/keymint/remote/Android.bp
+++ b/guest/hals/keymint/remote/Android.bp
@@ -32,9 +32,8 @@
         "-Wextra",
     ],
     shared_libs: [
-        "android.hardware.security.keymint-V1-ndk_platform",
-        "android.hardware.security.secureclock-V1-ndk_platform",
-        "android.hardware.security.sharedsecret-V1-ndk_platform",
+        "android.hardware.security.secureclock-V1-ndk",
+        "android.hardware.security.sharedsecret-V1-ndk",
         "lib_android_keymaster_keymint_utils",
         "libbase",
         "libbinder_ndk",
@@ -52,11 +51,24 @@
         "remote_keymint_device.cpp",
         "remote_keymint_operation.cpp",
         "remote_keymaster.cpp",
+        "remote_remotely_provisioned_component.cpp",
         "remote_secure_clock.cpp",
         "remote_shared_secret.cpp",
         "service.cpp",
     ],
     defaults: [
         "cuttlefish_guest_only",
+        "keymint_use_latest_hal_aidl_ndk_shared",
     ],
+    required: [
+        "RemoteProvisioner",
+        "android.hardware.hardware_keystore.remote-keymint.xml",
+    ],
+}
+
+prebuilt_etc {
+    name: "android.hardware.hardware_keystore.remote-keymint.xml",
+    sub_dir: "permissions",
+    vendor: true,
+    src: "android.hardware.hardware_keystore.remote-keymint.xml",
 }
diff --git a/guest/hals/keymint/remote/android.hardware.hardware_keystore.remote-keymint.xml b/guest/hals/keymint/remote/android.hardware.hardware_keystore.remote-keymint.xml
new file mode 100644
index 0000000..2ebf1fe
--- /dev/null
+++ b/guest/hals/keymint/remote/android.hardware.hardware_keystore.remote-keymint.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<permissions>
+  <feature name="android.hardware.hardware_keystore" version="200" />
+</permissions>
diff --git a/guest/hals/keymint/remote/android.hardware.security.keymint-service.remote.xml b/guest/hals/keymint/remote/android.hardware.security.keymint-service.remote.xml
index 4aa05ef..a4d0302 100644
--- a/guest/hals/keymint/remote/android.hardware.security.keymint-service.remote.xml
+++ b/guest/hals/keymint/remote/android.hardware.security.keymint-service.remote.xml
@@ -1,10 +1,12 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.security.keymint</name>
+        <version>2</version>
         <fqname>IKeyMintDevice/default</fqname>
     </hal>
     <hal format="aidl">
         <name>android.hardware.security.keymint</name>
+        <version>2</version>
         <fqname>IRemotelyProvisionedComponent/default</fqname>
     </hal>
 </manifest>
diff --git a/guest/hals/keymint/remote/remote_keymaster.cpp b/guest/hals/keymint/remote/remote_keymaster.cpp
index 5eee143..763c139 100644
--- a/guest/hals/keymint/remote/remote_keymaster.cpp
+++ b/guest/hals/keymint/remote/remote_keymaster.cpp
@@ -17,13 +17,15 @@
 #include "remote_keymaster.h"
 
 #include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
 #include <keymaster/android_keymaster_messages.h>
 #include <keymaster/keymaster_configuration.h>
 
 namespace keymaster {
 
 RemoteKeymaster::RemoteKeymaster(cuttlefish::KeymasterChannel* channel,
-                                 uint32_t message_version)
+                                 int32_t message_version)
     : channel_(channel), message_version_(message_version) {}
 
 RemoteKeymaster::~RemoteKeymaster() {}
@@ -67,6 +69,48 @@
     return false;
   }
 
+  // Set the vendor patchlevel to value retrieved from system property (which
+  // requires SELinux permission).
+  ConfigureVendorPatchlevelRequest vendor_req(message_version());
+  vendor_req.vendor_patchlevel = GetVendorPatchlevel();
+  ConfigureVendorPatchlevelResponse vendor_rsp =
+      ConfigureVendorPatchlevel(vendor_req);
+  if (vendor_rsp.error != KM_ERROR_OK) {
+    LOG(ERROR) << "Failed to configure keymaster vendor patchlevel: "
+               << vendor_rsp.error;
+    return false;
+  }
+
+  // Set the boot patchlevel to value retrieved from system property (which
+  // requires SELinux permission).
+  ConfigureBootPatchlevelRequest boot_req(message_version());
+  static constexpr char boot_prop_name[] = "ro.vendor.boot_security_patch";
+  auto boot_prop_value = android::base::GetProperty(boot_prop_name, "");
+  boot_prop_value = android::base::StringReplace(boot_prop_value.data(), "-",
+                                                 "", /* all */ true);
+  boot_req.boot_patchlevel = std::stoi(boot_prop_value);
+  ConfigureBootPatchlevelResponse boot_rsp = ConfigureBootPatchlevel(boot_req);
+  if (boot_rsp.error != KM_ERROR_OK) {
+    LOG(ERROR) << "Failed to configure keymaster boot patchlevel: "
+               << boot_rsp.error;
+    return false;
+  }
+
+  // Pass verified boot information to the remote KM implementation
+  auto vbmeta_digest = GetVbmetaDigest();
+  if (vbmeta_digest) {
+    ConfigureVerifiedBootInfoRequest request(
+        message_version(), GetVerifiedBootState(), GetBootloaderState(),
+        *vbmeta_digest);
+    ConfigureVerifiedBootInfoResponse response =
+        ConfigureVerifiedBootInfo(request);
+    if (response.error != KM_ERROR_OK) {
+      LOG(ERROR) << "Failed to configure keymaster verified boot info: "
+                 << response.error;
+      return false;
+    }
+  }
+
   return true;
 }
 
@@ -122,15 +166,25 @@
 
 void RemoteKeymaster::GenerateKey(const GenerateKeyRequest& request,
                                   GenerateKeyResponse* response) {
-  GenerateKeyRequest datedRequest(request.message_version);
-  datedRequest.key_description = request.key_description;
-
-  if (!request.key_description.Contains(TAG_CREATION_DATETIME)) {
+  if (message_version_ < MessageVersion(KmVersion::KEYMINT_1) &&
+      !request.key_description.Contains(TAG_CREATION_DATETIME)) {
+    GenerateKeyRequest datedRequest(request.message_version);
     datedRequest.key_description.push_back(TAG_CREATION_DATETIME,
                                            java_time(time(NULL)));
+    ForwardCommand(GENERATE_KEY, datedRequest, response);
+  } else {
+    ForwardCommand(GENERATE_KEY, request, response);
   }
+}
 
-  ForwardCommand(GENERATE_KEY, datedRequest, response);
+void RemoteKeymaster::GenerateRkpKey(const GenerateRkpKeyRequest& request,
+                                     GenerateRkpKeyResponse* response) {
+  ForwardCommand(GENERATE_RKP_KEY, request, response);
+}
+
+void RemoteKeymaster::GenerateCsr(const GenerateCsrRequest& request,
+                                  GenerateCsrResponse* response) {
+  ForwardCommand(GENERATE_CSR, request, response);
 }
 
 void RemoteKeymaster::GetKeyCharacteristics(
@@ -234,8 +288,35 @@
 void RemoteKeymaster::GenerateTimestampToken(
     GenerateTimestampTokenRequest& request,
     GenerateTimestampTokenResponse* response) {
-  // TODO(aosp/1641315): Send a message to the host.
   ForwardCommand(GENERATE_TIMESTAMP_TOKEN, request, response);
 }
 
+ConfigureVendorPatchlevelResponse RemoteKeymaster::ConfigureVendorPatchlevel(
+    const ConfigureVendorPatchlevelRequest& request) {
+  ConfigureVendorPatchlevelResponse response(message_version());
+  ForwardCommand(CONFIGURE_VENDOR_PATCHLEVEL, request, &response);
+  return response;
+}
+
+ConfigureBootPatchlevelResponse RemoteKeymaster::ConfigureBootPatchlevel(
+    const ConfigureBootPatchlevelRequest& request) {
+  ConfigureBootPatchlevelResponse response(message_version());
+  ForwardCommand(CONFIGURE_BOOT_PATCHLEVEL, request, &response);
+  return response;
+}
+
+ConfigureVerifiedBootInfoResponse RemoteKeymaster::ConfigureVerifiedBootInfo(
+    const ConfigureVerifiedBootInfoRequest& request) {
+  ConfigureVerifiedBootInfoResponse response(message_version());
+  ForwardCommand(CONFIGURE_VERIFIED_BOOT_INFO, request, &response);
+  return response;
+}
+
+GetRootOfTrustResponse RemoteKeymaster::GetRootOfTrust(
+    const GetRootOfTrustRequest& request) {
+  GetRootOfTrustResponse response(message_version());
+  ForwardCommand(GET_ROOT_OF_TRUST, request, &response);
+  return response;
+}
+
 }  // namespace keymaster
diff --git a/guest/hals/keymint/remote/remote_keymaster.h b/guest/hals/keymint/remote/remote_keymaster.h
index bd86012..2e0668f 100644
--- a/guest/hals/keymint/remote/remote_keymaster.h
+++ b/guest/hals/keymint/remote/remote_keymaster.h
@@ -26,14 +26,14 @@
 class RemoteKeymaster {
  private:
   cuttlefish::KeymasterChannel* channel_;
-  const uint32_t message_version_;
+  const int32_t message_version_;
 
   void ForwardCommand(AndroidKeymasterCommand command, const Serializable& req,
                       KeymasterResponse* rsp);
 
  public:
   RemoteKeymaster(cuttlefish::KeymasterChannel*,
-                  uint32_t message_version = kDefaultMessageVersion);
+                  int32_t message_version = kDefaultMessageVersion);
   ~RemoteKeymaster();
   bool Initialize();
   void GetVersion(const GetVersionRequest& request,
@@ -55,6 +55,10 @@
   void Configure(const ConfigureRequest& request, ConfigureResponse* response);
   void GenerateKey(const GenerateKeyRequest& request,
                    GenerateKeyResponse* response);
+  void GenerateRkpKey(const GenerateRkpKeyRequest& request,
+                      GenerateRkpKeyResponse* response);
+  void GenerateCsr(const GenerateCsrRequest& request,
+                   GenerateCsrResponse* response);
   void GetKeyCharacteristics(const GetKeyCharacteristicsRequest& request,
                              GetKeyCharacteristicsResponse* response);
   void ImportKey(const ImportKeyRequest& request, ImportKeyResponse* response);
@@ -82,12 +86,19 @@
       const VerifyAuthorizationRequest& request);
   DeviceLockedResponse DeviceLocked(const DeviceLockedRequest& request);
   EarlyBootEndedResponse EarlyBootEnded();
+  ConfigureVendorPatchlevelResponse ConfigureVendorPatchlevel(
+      const ConfigureVendorPatchlevelRequest& request);
+  ConfigureBootPatchlevelResponse ConfigureBootPatchlevel(
+      const ConfigureBootPatchlevelRequest& request);
+  ConfigureVerifiedBootInfoResponse ConfigureVerifiedBootInfo(
+      const ConfigureVerifiedBootInfoRequest& request);
   void GenerateTimestampToken(GenerateTimestampTokenRequest& request,
                               GenerateTimestampTokenResponse* response);
+  GetRootOfTrustResponse GetRootOfTrust(const GetRootOfTrustRequest& request);
 
   // CF HAL and remote sides are always compiled together, so will never
   // disagree about message versions.
-  uint32_t message_version() { return message_version_; }
+  int32_t message_version() { return message_version_; }
 };
 
 }  // namespace keymaster
diff --git a/guest/hals/keymint/remote/remote_keymint_device.cpp b/guest/hals/keymint/remote/remote_keymint_device.cpp
index 55903b7..c6db283 100644
--- a/guest/hals/keymint/remote/remote_keymint_device.cpp
+++ b/guest/hals/keymint/remote/remote_keymint_device.cpp
@@ -37,17 +37,19 @@
 namespace {
 
 vector<KeyCharacteristics> convertKeyCharacteristics(
-    SecurityLevel keyMintSecurityLevel, const AuthorizationSet& sw_enforced,
-    const AuthorizationSet& hw_enforced) {
+    const vector<KeyParameter>& keyParams, SecurityLevel keyMintSecurityLevel,
+    const AuthorizationSet& sw_enforced, const AuthorizationSet& hw_enforced,
+    bool include_keystore_enforced = true) {
   KeyCharacteristics keyMintEnforced{keyMintSecurityLevel, {}};
 
   if (keyMintSecurityLevel != SecurityLevel::SOFTWARE) {
     // We're pretending to be TRUSTED_ENVIRONMENT or STRONGBOX.
     keyMintEnforced.authorizations = kmParamSet2Aidl(hw_enforced);
-    // Put all the software authorizations in the keystore list.
-    KeyCharacteristics keystoreEnforced{SecurityLevel::KEYSTORE,
-                                        kmParamSet2Aidl(sw_enforced)};
-    return {std::move(keyMintEnforced), std::move(keystoreEnforced)};
+    if (include_keystore_enforced && !sw_enforced.empty()) {
+      return {std::move(keyMintEnforced),
+              {SecurityLevel::KEYSTORE, kmParamSet2Aidl(sw_enforced)}};
+    }
+    return {std::move(keyMintEnforced)};
   }
 
   KeyCharacteristics keystoreEnforced{SecurityLevel::KEYSTORE, {}};
@@ -77,6 +79,11 @@
 
       /* Unenforceable */
       case KM_TAG_CREATION_DATETIME:
+        for (const auto& p : keyParams) {
+          if (p.tag == Tag::CREATION_DATETIME) {
+            keystoreEnforced.authorizations.push_back(kmParam2Aidl(entry));
+          }
+        }
         break;
 
       /* Disallowed in KeyCharacteristics */
@@ -160,10 +167,12 @@
 
   vector<KeyCharacteristics> retval;
   retval.reserve(2);
-  if (!keyMintEnforced.authorizations.empty())
+  if (!keyMintEnforced.authorizations.empty()) {
     retval.push_back(std::move(keyMintEnforced));
-  if (!keystoreEnforced.authorizations.empty())
+  }
+  if (include_keystore_enforced && !keystoreEnforced.authorizations.empty()) {
     retval.push_back(std::move(keystoreEnforced));
+  }
 
   return retval;
 }
@@ -245,7 +254,7 @@
 
   creationResult->keyBlob = kmBlob2vector(response.key_blob);
   creationResult->keyCharacteristics = convertKeyCharacteristics(
-      securityLevel_, response.unenforced, response.enforced);
+      keyParams, securityLevel_, response.unenforced, response.enforced);
   creationResult->certificateChain =
       convertCertificateChain(response.certificate_chain);
   return ScopedAStatus::ok();
@@ -279,7 +288,7 @@
 
   creationResult->keyBlob = kmBlob2vector(response.key_blob);
   creationResult->keyCharacteristics = convertKeyCharacteristics(
-      securityLevel_, response.unenforced, response.enforced);
+      keyParams, securityLevel_, response.unenforced, response.enforced);
   creationResult->certificateChain =
       convertCertificateChain(response.certificate_chain);
 
@@ -310,7 +319,7 @@
 
   creationResult->keyBlob = kmBlob2vector(response.key_blob);
   creationResult->keyCharacteristics = convertKeyCharacteristics(
-      securityLevel_, response.unenforced, response.enforced);
+      unwrappingParams, securityLevel_, response.unenforced, response.enforced);
   creationResult->certificateChain =
       convertCertificateChain(response.certificate_chain);
 
@@ -413,10 +422,52 @@
 }
 
 ScopedAStatus RemoteKeyMintDevice::getKeyCharacteristics(
-    const std::vector<uint8_t>& /* storageKeyBlob */,
-    const std::vector<uint8_t>& /* appId */,
-    const std::vector<uint8_t>& /* appData */,
-    std::vector<KeyCharacteristics>* /* keyCharacteristics */) {
+    const std::vector<uint8_t>& storageKeyBlob,
+    const std::vector<uint8_t>& appId, const std::vector<uint8_t>& appData,
+    std::vector<KeyCharacteristics>* keyCharacteristics) {
+  GetKeyCharacteristicsRequest request(impl_.message_version());
+  request.SetKeyMaterial(storageKeyBlob.data(), storageKeyBlob.size());
+  addClientAndAppData(appId, appData, &request.additional_params);
+
+  GetKeyCharacteristicsResponse response(impl_.message_version());
+  impl_.GetKeyCharacteristics(request, &response);
+
+  if (response.error != KM_ERROR_OK) {
+    return kmError2ScopedAStatus(response.error);
+  }
+
+  *keyCharacteristics = convertKeyCharacteristics(
+      {} /*keyParams*/, securityLevel_, response.unenforced, response.enforced,
+      false /*include_keystore_enforced*/);
+
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteKeyMintDevice::getRootOfTrustChallenge(
+    std::array<uint8_t, 16>* /* challenge */) {
   return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
 }
+
+ScopedAStatus RemoteKeyMintDevice::getRootOfTrust(
+    const std::array<uint8_t, 16>& challenge,
+    std::vector<uint8_t>* rootOfTrust) {
+  if (!rootOfTrust) {
+    return kmError2ScopedAStatus(KM_ERROR_UNEXPECTED_NULL_POINTER);
+  }
+  GetRootOfTrustRequest request(impl_.message_version(),
+                                {challenge.begin(), challenge.end()});
+  GetRootOfTrustResponse response = impl_.GetRootOfTrust(request);
+  if (response.error != KM_ERROR_OK) {
+    return kmError2ScopedAStatus(response.error);
+  }
+
+  *rootOfTrust = std::move(response.rootOfTrust);
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteKeyMintDevice::sendRootOfTrust(
+    const std::vector<uint8_t>& /* rootOfTrust */) {
+  return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
+}
+
 }  // namespace aidl::android::hardware::security::keymint
diff --git a/guest/hals/keymint/remote/remote_keymint_device.h b/guest/hals/keymint/remote/remote_keymint_device.h
index c3ebad1..a7a8293 100644
--- a/guest/hals/keymint/remote/remote_keymint_device.h
+++ b/guest/hals/keymint/remote/remote_keymint_device.h
@@ -82,6 +82,13 @@
       const std::vector<uint8_t>& appId, const std::vector<uint8_t>& appData,
       std::vector<KeyCharacteristics>* keyCharacteristics) override;
 
+  ScopedAStatus getRootOfTrustChallenge(
+      std::array<uint8_t, 16>* challenge) override;
+  ScopedAStatus getRootOfTrust(const std::array<uint8_t, 16>& challenge,
+                               std::vector<uint8_t>* rootOfTrust) override;
+  ScopedAStatus sendRootOfTrust(
+      const std::vector<uint8_t>& rootOfTrust) override;
+
  protected:
   ::keymaster::RemoteKeymaster& impl_;
   SecurityLevel securityLevel_;
diff --git a/guest/hals/keymint/remote/remote_remotely_provisioned_component.cpp b/guest/hals/keymint/remote/remote_remotely_provisioned_component.cpp
new file mode 100644
index 0000000..4098362
--- /dev/null
+++ b/guest/hals/keymint/remote/remote_remotely_provisioned_component.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <keymaster/cppcose/cppcose.h>
+#include <keymaster/keymaster_configuration.h>
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+
+#include <variant>
+
+#include "KeyMintUtils.h"
+#include "android/binder_auto_utils.h"
+#include "remote_remotely_provisioned_component.h"
+
+namespace aidl::android::hardware::security::keymint {
+namespace {
+using namespace cppcose;
+using namespace keymaster;
+
+using ::aidl::android::hardware::security::keymint::km_utils::kmBlob2vector;
+using ::ndk::ScopedAStatus;
+
+// Error codes from the provisioning stack are negated.
+ndk::ScopedAStatus toKeymasterError(const KeymasterResponse& response) {
+  auto error =
+      static_cast<keymaster_error_t>(-static_cast<int32_t>(response.error));
+  return ::aidl::android::hardware::security::keymint::km_utils::
+      kmError2ScopedAStatus(error);
+}
+
+}  // namespace
+
+RemoteRemotelyProvisionedComponent::RemoteRemotelyProvisionedComponent(
+    keymaster::RemoteKeymaster& impl)
+    : impl_(impl) {}
+
+ScopedAStatus RemoteRemotelyProvisionedComponent::getHardwareInfo(
+    RpcHardwareInfo* info) {
+  info->versionNumber = 2;
+  info->rpcAuthorName = "Google";
+  info->supportedEekCurve = RpcHardwareInfo::CURVE_25519;
+  info->uniqueId = "remote keymint";
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteRemotelyProvisionedComponent::generateEcdsaP256KeyPair(
+    bool testMode, MacedPublicKey* macedPublicKey,
+    std::vector<uint8_t>* privateKeyHandle) {
+  GenerateRkpKeyRequest request(impl_.message_version());
+  request.test_mode = testMode;
+  GenerateRkpKeyResponse response(impl_.message_version());
+  impl_.GenerateRkpKey(request, &response);
+  if (response.error != KM_ERROR_OK) {
+    return toKeymasterError(response);
+  }
+
+  macedPublicKey->macedKey = km_utils::kmBlob2vector(response.maced_public_key);
+  *privateKeyHandle = km_utils::kmBlob2vector(response.key_blob);
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteRemotelyProvisionedComponent::generateCertificateRequest(
+    bool testMode, const std::vector<MacedPublicKey>& keysToSign,
+    const std::vector<uint8_t>& endpointEncCertChain,
+    const std::vector<uint8_t>& challenge, DeviceInfo* deviceInfo,
+    ProtectedData* protectedData, std::vector<uint8_t>* keysToSignMac) {
+  GenerateCsrRequest request(impl_.message_version());
+  request.test_mode = testMode;
+  request.num_keys = keysToSign.size();
+  request.keys_to_sign_array = new KeymasterBlob[keysToSign.size()];
+  for (size_t i = 0; i < keysToSign.size(); i++) {
+    request.SetKeyToSign(i, keysToSign[i].macedKey.data(),
+                         keysToSign[i].macedKey.size());
+  }
+  request.SetEndpointEncCertChain(endpointEncCertChain.data(),
+                                  endpointEncCertChain.size());
+  request.SetChallenge(challenge.data(), challenge.size());
+  GenerateCsrResponse response(impl_.message_version());
+  impl_.GenerateCsr(request, &response);
+
+  if (response.error != KM_ERROR_OK) {
+    return toKeymasterError(response);
+  }
+  deviceInfo->deviceInfo = km_utils::kmBlob2vector(response.device_info_blob);
+  protectedData->protectedData =
+      km_utils::kmBlob2vector(response.protected_data_blob);
+  *keysToSignMac = km_utils::kmBlob2vector(response.keys_to_sign_mac);
+  return ScopedAStatus::ok();
+}
+
+}  // namespace aidl::android::hardware::security::keymint
diff --git a/guest/hals/keymint/remote/remote_remotely_provisioned_component.h b/guest/hals/keymint/remote/remote_remotely_provisioned_component.h
new file mode 100644
index 0000000..35e182a
--- /dev/null
+++ b/guest/hals/keymint/remote/remote_remotely_provisioned_component.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <vector>
+
+#include <aidl/android/hardware/security/keymint/BnRemotelyProvisionedComponent.h>
+#include <aidl/android/hardware/security/keymint/MacedPublicKey.h>
+#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
+#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
+
+#include "KeyMintUtils.h"
+#include "guest/hals/keymint/remote/remote_keymaster.h"
+
+namespace aidl::android::hardware::security::keymint {
+
+class RemoteRemotelyProvisionedComponent
+    : public BnRemotelyProvisionedComponent {
+ public:
+  explicit RemoteRemotelyProvisionedComponent(keymaster::RemoteKeymaster& impl);
+
+  ndk::ScopedAStatus getHardwareInfo(RpcHardwareInfo* info) override;
+
+  ndk::ScopedAStatus generateEcdsaP256KeyPair(
+      bool testMode, MacedPublicKey* macedPublicKey,
+      std::vector<uint8_t>* privateKeyHandle) override;
+
+  ndk::ScopedAStatus generateCertificateRequest(
+      bool testMode, const std::vector<MacedPublicKey>& keysToSign,
+      const std::vector<uint8_t>& endpointEncCertChain,
+      const std::vector<uint8_t>& challenge, DeviceInfo* deviceInfo,
+      ProtectedData* protectedData,
+      std::vector<uint8_t>* keysToSignMac) override;
+
+ private:
+  keymaster::RemoteKeymaster& impl_;
+};
+
+}  // namespace aidl::android::hardware::security::keymint
diff --git a/guest/hals/keymint/remote/service.cpp b/guest/hals/keymint/remote/service.cpp
index 404df77..1fb0143 100644
--- a/guest/hals/keymint/remote/service.cpp
+++ b/guest/hals/keymint/remote/service.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "android.hardware.security.keymint-service"
+#define LOG_TAG "android.hardware.security.keymint-service.remote"
 
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
@@ -23,24 +23,31 @@
 #include <keymaster/android_keymaster_messages.h>
 #include <keymaster/km_version.h>
 #include <keymaster/soft_keymaster_logger.h>
-#include "guest/hals/keymint/remote/remote_keymint_device.h"
 
+#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
 #include <guest/hals/keymint/remote/remote_keymaster.h>
 #include <guest/hals/keymint/remote/remote_keymint_device.h>
+#include <guest/hals/keymint/remote/remote_remotely_provisioned_component.h>
 #include <guest/hals/keymint/remote/remote_secure_clock.h>
 #include <guest/hals/keymint/remote/remote_shared_secret.h>
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/security/keymaster_channel.h"
 
-static const char device[] = "/dev/hvc3";
+namespace {
+
+const char device[] = "/dev/hvc3";
 
 using aidl::android::hardware::security::keymint::RemoteKeyMintDevice;
+using aidl::android::hardware::security::keymint::
+    RemoteRemotelyProvisionedComponent;
 using aidl::android::hardware::security::keymint::SecurityLevel;
 using aidl::android::hardware::security::secureclock::RemoteSecureClock;
 using aidl::android::hardware::security::sharedsecret::RemoteSharedSecret;
+using keymaster::GenerateTimestampTokenRequest;
+using keymaster::GenerateTimestampTokenResponse;
 
 template <typename T, class... Args>
-static std::shared_ptr<T> addService(Args&&... args) {
+std::shared_ptr<T> addService(Args&&... args) {
   std::shared_ptr<T> ser =
       ndk::SharedRefBase::make<T>(std::forward<Args>(args)...);
   auto instanceName = std::string(T::descriptor) + "/default";
@@ -51,6 +58,21 @@
   return ser;
 }
 
+SecurityLevel getSecurityLevel(::keymaster::RemoteKeymaster& remote_keymaster) {
+  GenerateTimestampTokenRequest request(remote_keymaster.message_version());
+  GenerateTimestampTokenResponse response(remote_keymaster.message_version());
+  remote_keymaster.GenerateTimestampToken(request, &response);
+
+  if (response.error != STATUS_OK) {
+    LOG(FATAL) << "Error getting timestamp token from remote keymaster: "
+               << response.error;
+  }
+
+  return static_cast<SecurityLevel>(response.token.security_level);
+}
+
+}  // namespace
+
 int main(int, char** argv) {
   android::base::InitLogging(argv, android::base::KernelLogger);
   // Zero threads seems like a useless pool, but below we'll join this thread to
@@ -71,12 +93,17 @@
 
   keymaster::RemoteKeymaster remote_keymaster(
       &keymasterChannel, keymaster::MessageVersion(
-                             keymaster::KmVersion::KEYMINT_1, 0 /* km_date */));
+                             keymaster::KmVersion::KEYMINT_2, 0 /* km_date */));
+
+  if (!remote_keymaster.Initialize()) {
+    LOG(FATAL) << "Could not initialize keymaster";
+  }
 
   addService<RemoteKeyMintDevice>(remote_keymaster,
-                                  SecurityLevel::TRUSTED_ENVIRONMENT);
+                                  getSecurityLevel(remote_keymaster));
   addService<RemoteSecureClock>(remote_keymaster);
   addService<RemoteSharedSecret>(remote_keymaster);
+  addService<RemoteRemotelyProvisionedComponent>(remote_keymaster);
 
   ABinderProcess_joinThreadPool();
   return EXIT_FAILURE;  // should not reach
diff --git a/guest/hals/nfc/Android.bp b/guest/hals/nfc/Android.bp
new file mode 100644
index 0000000..bf71656
--- /dev/null
+++ b/guest/hals/nfc/Android.bp
@@ -0,0 +1,30 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "android.hardware.nfc-service.cuttlefish",
+    relative_install_path: "hw",
+    init_rc: ["nfc-service-cuttlefish.rc"],
+    vintf_fragments: ["nfc-service-cuttlefish.xml"],
+    vendor: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libutils",
+        "libbinder_ndk",
+        "android.hardware.nfc-V1-ndk",
+    ],
+    required: [
+        "libnfc-hal-cf.conf-default",
+    ],
+    srcs: [
+        "main.cc",
+        "Nfc.cc",
+        "Cf_hal_api.cc",
+    ],
+}
diff --git a/guest/hals/nfc/Cf_hal_api.cc b/guest/hals/nfc/Cf_hal_api.cc
new file mode 100644
index 0000000..ed564d2
--- /dev/null
+++ b/guest/hals/nfc/Cf_hal_api.cc
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <pthread.h>
+#include <string.h>
+
+#include "Cf_hal_api.h"
+#include "hardware_nfc.h"
+
+using android::base::StringPrintf;
+
+bool hal_opened = false;
+bool dbg_logging = false;
+pthread_mutex_t hmutex = PTHREAD_MUTEX_INITIALIZER;
+nfc_stack_callback_t* e_cback;
+nfc_stack_data_callback_t* d_cback;
+
+static struct aidl_callback_struct {
+  pthread_mutex_t mutex;
+  pthread_cond_t cond;
+  pthread_t thr;
+  int event_pending;
+  int stop_thread;
+  int thread_running;
+  nfc_event_t event;
+  nfc_status_t event_status;
+} aidl_callback_data;
+
+static void* aidl_callback_thread_fct(void* arg) {
+  int ret;
+  struct aidl_callback_struct* pcb_data = (struct aidl_callback_struct*)arg;
+
+  ret = pthread_mutex_lock(&pcb_data->mutex);
+  if (ret != 0) {
+    LOG(ERROR) << StringPrintf("%s pthread_mutex_lock failed", __func__);
+    goto error;
+  }
+
+  do {
+    if (pcb_data->event_pending == 0) {
+      ret = pthread_cond_wait(&pcb_data->cond, &pcb_data->mutex);
+      if (ret != 0) {
+        LOG(ERROR) << StringPrintf("%s pthread_cond_wait failed", __func__);
+        break;
+      }
+    }
+
+    if (pcb_data->event_pending) {
+      nfc_event_t event = pcb_data->event;
+      nfc_status_t event_status = pcb_data->event_status;
+      int ending = pcb_data->stop_thread;
+      pcb_data->event_pending = 0;
+      ret = pthread_cond_signal(&pcb_data->cond);
+      if (ret != 0) {
+        LOG(ERROR) << StringPrintf("%s pthread_cond_signal failed", __func__);
+        break;
+      }
+      if (ending) {
+        pcb_data->thread_running = 0;
+      }
+      ret = pthread_mutex_unlock(&pcb_data->mutex);
+      if (ret != 0) {
+        LOG(ERROR) << StringPrintf("%s pthread_mutex_unlock failed", __func__);
+      }
+      LOG(INFO) << StringPrintf("%s event %hhx status %hhx", __func__, event,
+                                event_status);
+      e_cback(event, event_status);
+      usleep(50000);
+      if (ending) {
+        return NULL;
+      }
+      ret = pthread_mutex_lock(&pcb_data->mutex);
+      if (ret != 0) {
+        LOG(ERROR) << StringPrintf("%s pthread_mutex_lock failed", __func__);
+        goto error;
+      }
+    }
+  } while (pcb_data->stop_thread == 0 || pcb_data->event_pending);
+
+  ret = pthread_mutex_unlock(&pcb_data->mutex);
+  if (ret != 0) {
+    LOG(ERROR) << StringPrintf("%s pthread_mutex_unlock failed", __func__);
+  }
+
+error:
+  pcb_data->thread_running = 0;
+  return NULL;
+}
+
+static int aidl_callback_thread_start() {
+  int ret;
+  LOG(INFO) << StringPrintf("%s", __func__);
+
+  memset(&aidl_callback_data, 0, sizeof(aidl_callback_data));
+
+  ret = pthread_mutex_init(&aidl_callback_data.mutex, NULL);
+  if (ret != 0) {
+    LOG(ERROR) << StringPrintf("%s pthread_mutex_init failed", __func__);
+    return ret;
+  }
+
+  ret = pthread_cond_init(&aidl_callback_data.cond, NULL);
+  if (ret != 0) {
+    LOG(ERROR) << StringPrintf("%s pthread_cond_init failed", __func__);
+    return ret;
+  }
+
+  aidl_callback_data.thread_running = 1;
+
+  ret = pthread_create(&aidl_callback_data.thr, NULL, aidl_callback_thread_fct,
+                       &aidl_callback_data);
+  if (ret != 0) {
+    LOG(ERROR) << StringPrintf("%s pthread_create failed", __func__);
+    aidl_callback_data.thread_running = 0;
+    return ret;
+  }
+
+  return 0;
+}
+
+static int aidl_callback_thread_end() {
+  LOG(INFO) << StringPrintf("%s", __func__);
+  if (aidl_callback_data.thread_running != 0) {
+    int ret;
+
+    ret = pthread_mutex_lock(&aidl_callback_data.mutex);
+    if (ret != 0) {
+      LOG(ERROR) << StringPrintf("%s pthread_mutex_lock failed", __func__);
+      return ret;
+    }
+
+    aidl_callback_data.stop_thread = 1;
+
+    // Wait for the thread to have no event pending
+    while (aidl_callback_data.thread_running &&
+           aidl_callback_data.event_pending) {
+      ret = pthread_cond_signal(&aidl_callback_data.cond);
+      if (ret != 0) {
+        LOG(ERROR) << StringPrintf("%s pthread_cond_signal failed", __func__);
+        return ret;
+      }
+      ret = pthread_cond_wait(&aidl_callback_data.cond,
+                              &aidl_callback_data.mutex);
+      if (ret != 0) {
+        LOG(ERROR) << StringPrintf("%s pthread_cond_wait failed", __func__);
+        break;
+      }
+    }
+
+    ret = pthread_mutex_unlock(&aidl_callback_data.mutex);
+    if (ret != 0) {
+      LOG(ERROR) << StringPrintf("%s pthread_mutex_unlock failed", __func__);
+      return ret;
+    }
+
+    ret = pthread_cond_signal(&aidl_callback_data.cond);
+    if (ret != 0) {
+      LOG(ERROR) << StringPrintf("%s pthread_cond_signal failed", __func__);
+      return ret;
+    }
+
+    ret = pthread_detach(aidl_callback_data.thr);
+    if (ret != 0) {
+      LOG(ERROR) << StringPrintf("%s pthread_detach failed", __func__);
+      return ret;
+    }
+  }
+  return 0;
+}
+
+static void aidl_callback_post(nfc_event_t event, nfc_status_t event_status) {
+  int ret;
+
+  if (pthread_equal(pthread_self(), aidl_callback_data.thr)) {
+    e_cback(event, event_status);
+  }
+
+  ret = pthread_mutex_lock(&aidl_callback_data.mutex);
+  if (ret != 0) {
+    LOG(ERROR) << StringPrintf("%s pthread_mutex_lock failed", __func__);
+    return;
+  }
+
+  if (aidl_callback_data.thread_running == 0) {
+    (void)pthread_mutex_unlock(&aidl_callback_data.mutex);
+    LOG(ERROR) << StringPrintf("%s thread is not running", __func__);
+    e_cback(event, event_status);
+    return;
+  }
+
+  while (aidl_callback_data.event_pending) {
+    ret =
+        pthread_cond_wait(&aidl_callback_data.cond, &aidl_callback_data.mutex);
+    if (ret != 0) {
+      LOG(ERROR) << StringPrintf("%s pthread_cond_wait failed", __func__);
+      return;
+    }
+  }
+
+  aidl_callback_data.event_pending = 1;
+  aidl_callback_data.event = event;
+  aidl_callback_data.event_status = event_status;
+
+  ret = pthread_mutex_unlock(&aidl_callback_data.mutex);
+  if (ret != 0) {
+    LOG(ERROR) << StringPrintf("%s pthread_mutex_unlock failed", __func__);
+    return;
+  }
+
+  ret = pthread_cond_signal(&aidl_callback_data.cond);
+  if (ret != 0) {
+    LOG(ERROR) << StringPrintf("%s pthread_cond_signal failed", __func__);
+    return;
+  }
+}
+
+int Cf_hal_open(nfc_stack_callback_t* p_cback,
+                nfc_stack_data_callback_t* p_data_cback) {
+  LOG(INFO) << StringPrintf("%s", __func__);
+  pthread_mutex_lock(&hmutex);
+  if (hal_opened) {
+    // already opened, close then open again
+    LOG(INFO) << StringPrintf("%s close and open again", __func__);
+    if (aidl_callback_data.thread_running && aidl_callback_thread_end() != 0) {
+      pthread_mutex_unlock(&hmutex);
+      return -1;
+    }
+    hal_opened = false;
+  }
+  e_cback = p_cback;
+  d_cback = p_data_cback;
+  if ((hal_opened || !aidl_callback_data.thread_running) &&
+      (aidl_callback_thread_start() != 0)) {
+    // status failed
+    LOG(INFO) << StringPrintf("%s failed", __func__);
+    aidl_callback_post(HAL_NFC_OPEN_CPLT_EVT, HAL_NFC_STATUS_FAILED);
+    pthread_mutex_unlock(&hmutex);
+    return -1;
+  }
+  hal_opened = true;
+  aidl_callback_post(HAL_NFC_OPEN_CPLT_EVT, HAL_NFC_STATUS_OK);
+  pthread_mutex_unlock(&hmutex);
+  return 0;
+}
+
+int Cf_hal_write(uint16_t data_len, const uint8_t* p_data) {
+  if (!hal_opened) return -1;
+  // TODO: write NCI state machine
+  (void)data_len;
+  (void)p_data;
+  return 0;
+}
+
+int Cf_hal_core_initialized() {
+  if (!hal_opened) return -1;
+  pthread_mutex_lock(&hmutex);
+  aidl_callback_post(HAL_NFC_POST_INIT_CPLT_EVT, HAL_NFC_STATUS_OK);
+  pthread_mutex_unlock(&hmutex);
+  return 0;
+}
+
+int Cf_hal_pre_discover() {
+  if (!hal_opened) return -1;
+  pthread_mutex_lock(&hmutex);
+  aidl_callback_post(HAL_NFC_PRE_DISCOVER_CPLT_EVT, HAL_NFC_STATUS_OK);
+  pthread_mutex_unlock(&hmutex);
+  return 0;
+}
+
+int Cf_hal_close() {
+  LOG(INFO) << StringPrintf("%s", __func__);
+  if (!hal_opened) return -1;
+  pthread_mutex_lock(&hmutex);
+  hal_opened = false;
+  aidl_callback_post(HAL_NFC_CLOSE_CPLT_EVT, HAL_NFC_STATUS_OK);
+  if (aidl_callback_data.thread_running && aidl_callback_thread_end() != 0) {
+    LOG(ERROR) << StringPrintf("%s thread end failed", __func__);
+    pthread_mutex_unlock(&hmutex);
+    return -1;
+  }
+  pthread_mutex_unlock(&hmutex);
+  return 0;
+}
+
+int Cf_hal_close_off() {
+  LOG(INFO) << StringPrintf("%s", __func__);
+  if (!hal_opened) return -1;
+  pthread_mutex_lock(&hmutex);
+  hal_opened = false;
+  aidl_callback_post(HAL_NFC_CLOSE_CPLT_EVT, HAL_NFC_STATUS_OK);
+  if (aidl_callback_data.thread_running && aidl_callback_thread_end() != 0) {
+    LOG(ERROR) << StringPrintf("%s thread end failed", __func__);
+    pthread_mutex_unlock(&hmutex);
+    return -1;
+  }
+  pthread_mutex_unlock(&hmutex);
+  return 0;
+}
+
+int Cf_hal_power_cycle() {
+  if (!hal_opened) return -1;
+  pthread_mutex_lock(&hmutex);
+  aidl_callback_post(HAL_NFC_OPEN_CPLT_EVT, HAL_NFC_STATUS_OK);
+  pthread_mutex_unlock(&hmutex);
+  return 0;
+}
+
+void Cf_hal_factoryReset() {}
+void Cf_hal_getConfig(NfcConfig& config) {
+  // TODO: read config from /vendor/etc/libnfc-hal-cf.conf
+  memset(&config, 0x00, sizeof(NfcConfig));
+  config.nfaPollBailOutMode = 1;
+  config.maxIsoDepTransceiveLength = 0xFEFF;
+  config.defaultOffHostRoute = 0x81;
+  config.defaultOffHostRouteFelica = 0x81;
+  config.defaultSystemCodeRoute = 0x00;
+  config.defaultSystemCodePowerState = 0x3B;
+  config.defaultRoute = 0x00;
+  config.offHostRouteUicc.resize(1);
+  config.offHostRouteUicc[0] = 0x81;
+  config.offHostRouteEse.resize(1);
+  config.offHostRouteEse[0] = 0x81;
+  config.defaultIsoDepRoute = 0x81;
+}
+
+void Cf_hal_setVerboseLogging(bool enable) { dbg_logging = enable; }
+
+bool Cf_hal_getVerboseLogging() { return dbg_logging; }
diff --git a/guest/hals/nfc/Cf_hal_api.h b/guest/hals/nfc/Cf_hal_api.h
new file mode 100644
index 0000000..519e853
--- /dev/null
+++ b/guest/hals/nfc/Cf_hal_api.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _VENDOR_HAL_API_H_
+#define _VENDOR_HAL_API_H_
+
+#include <aidl/android/hardware/nfc/INfc.h>
+#include <aidl/android/hardware/nfc/NfcConfig.h>
+#include <aidl/android/hardware/nfc/NfcEvent.h>
+#include <aidl/android/hardware/nfc/NfcStatus.h>
+#include <aidl/android/hardware/nfc/PresenceCheckAlgorithm.h>
+#include <aidl/android/hardware/nfc/ProtocolDiscoveryConfig.h>
+#include "hardware_nfc.h"
+
+using aidl::android::hardware::nfc::NfcConfig;
+using aidl::android::hardware::nfc::NfcEvent;
+using aidl::android::hardware::nfc::NfcStatus;
+using aidl::android::hardware::nfc::PresenceCheckAlgorithm;
+using aidl::android::hardware::nfc::ProtocolDiscoveryConfig;
+
+int Cf_hal_open(nfc_stack_callback_t* p_cback,
+                nfc_stack_data_callback_t* p_data_cback);
+int Cf_hal_write(uint16_t data_len, const uint8_t* p_data);
+
+int Cf_hal_core_initialized();
+
+int Cf_hal_pre_discover();
+
+int Cf_hal_close();
+
+int Cf_hal_close_off();
+
+int Cf_hal_power_cycle();
+
+void Cf_hal_factoryReset();
+
+void Cf_hal_getConfig(NfcConfig& config);
+
+void Cf_hal_setVerboseLogging(bool enable);
+
+bool Cf_hal_getVerboseLogging();
+
+#endif /* _VENDOR_HAL_API_H_ */
diff --git a/guest/hals/nfc/Nfc.cc b/guest/hals/nfc/Nfc.cc
new file mode 100644
index 0000000..993d80b
--- /dev/null
+++ b/guest/hals/nfc/Nfc.cc
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Nfc.h"
+
+#include <android-base/logging.h>
+
+#include "Cf_hal_api.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace nfc {
+
+std::shared_ptr<INfcClientCallback> Nfc::mCallback = nullptr;
+AIBinder_DeathRecipient* clientDeathRecipient = nullptr;
+
+void OnDeath(void* cookie) {
+  if (Nfc::mCallback != nullptr &&
+      !AIBinder_isAlive(Nfc::mCallback->asBinder().get())) {
+    LOG(INFO) << __func__ << " Nfc service has died";
+    Nfc* nfc = static_cast<Nfc*>(cookie);
+    nfc->close(NfcCloseType::DISABLE);
+  }
+}
+
+::ndk::ScopedAStatus Nfc::open(
+    const std::shared_ptr<INfcClientCallback>& clientCallback) {
+  LOG(INFO) << "open";
+  if (clientCallback == nullptr) {
+    LOG(INFO) << "Nfc::open null callback";
+    return ndk::ScopedAStatus::fromServiceSpecificError(
+        static_cast<int32_t>(NfcStatus::FAILED));
+  } else {
+    Nfc::mCallback = clientCallback;
+
+    if (clientDeathRecipient != nullptr) {
+      AIBinder_DeathRecipient_delete(clientDeathRecipient);
+      clientDeathRecipient = nullptr;
+    }
+    clientDeathRecipient = AIBinder_DeathRecipient_new(OnDeath);
+    auto linkRet =
+        AIBinder_linkToDeath(clientCallback->asBinder().get(),
+                             clientDeathRecipient, this /* cookie */);
+    if (linkRet != STATUS_OK) {
+      LOG(ERROR) << __func__ << ": linkToDeath failed: " << linkRet;
+      // Just ignore the error.
+    }
+
+    int ret = Cf_hal_open(eventCallback, dataCallback);
+    return ret == 0 ? ndk::ScopedAStatus::ok()
+                    : ndk::ScopedAStatus::fromServiceSpecificError(
+                          static_cast<int32_t>(NfcStatus::FAILED));
+    return ndk::ScopedAStatus::ok();
+  }
+  return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::close(NfcCloseType type) {
+  LOG(INFO) << "close";
+  if (Nfc::mCallback == nullptr) {
+    LOG(ERROR) << __func__ << " mCallback null";
+    return ndk::ScopedAStatus::fromServiceSpecificError(
+        static_cast<int32_t>(NfcStatus::FAILED));
+  }
+  int ret = 0;
+  if (type == NfcCloseType::HOST_SWITCHED_OFF) {
+    ret = Cf_hal_close_off();
+  } else {
+    ret = Cf_hal_close();
+  }
+  AIBinder_DeathRecipient_delete(clientDeathRecipient);
+  clientDeathRecipient = nullptr;
+  return ret == 0 ? ndk::ScopedAStatus::ok()
+                  : ndk::ScopedAStatus::fromServiceSpecificError(
+                        static_cast<int32_t>(NfcStatus::FAILED));
+}
+
+::ndk::ScopedAStatus Nfc::coreInitialized() {
+  LOG(INFO) << "coreInitialized";
+  if (Nfc::mCallback == nullptr) {
+    LOG(ERROR) << __func__ << "mCallback null";
+    return ndk::ScopedAStatus::fromServiceSpecificError(
+        static_cast<int32_t>(NfcStatus::FAILED));
+  }
+  int ret = Cf_hal_core_initialized();
+
+  return ret == 0 ? ndk::ScopedAStatus::ok()
+                  : ndk::ScopedAStatus::fromServiceSpecificError(
+                        static_cast<int32_t>(NfcStatus::FAILED));
+}
+
+::ndk::ScopedAStatus Nfc::factoryReset() {
+  LOG(INFO) << "factoryReset";
+  Cf_hal_factoryReset();
+  return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::getConfig(NfcConfig* _aidl_return) {
+  LOG(INFO) << "getConfig";
+  NfcConfig nfcVendorConfig;
+  Cf_hal_getConfig(nfcVendorConfig);
+
+  *_aidl_return = nfcVendorConfig;
+  return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::powerCycle() {
+  LOG(INFO) << "powerCycle";
+  if (Nfc::mCallback == nullptr) {
+    LOG(ERROR) << __func__ << "mCallback null";
+    return ndk::ScopedAStatus::fromServiceSpecificError(
+        static_cast<int32_t>(NfcStatus::FAILED));
+  }
+  return Cf_hal_power_cycle() ? ndk::ScopedAStatus::fromServiceSpecificError(
+                                    static_cast<int32_t>(NfcStatus::FAILED))
+                              : ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::preDiscover() {
+  LOG(INFO) << "preDiscover";
+  if (Nfc::mCallback == nullptr) {
+    LOG(ERROR) << __func__ << "mCallback null";
+    return ndk::ScopedAStatus::fromServiceSpecificError(
+        static_cast<int32_t>(NfcStatus::FAILED));
+  }
+  return Cf_hal_pre_discover() ? ndk::ScopedAStatus::fromServiceSpecificError(
+                                     static_cast<int32_t>(NfcStatus::FAILED))
+                               : ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::write(const std::vector<uint8_t>& data,
+                                int32_t* _aidl_return) {
+  LOG(INFO) << "write";
+  if (Nfc::mCallback == nullptr) {
+    LOG(ERROR) << __func__ << "mCallback null";
+    return ndk::ScopedAStatus::fromServiceSpecificError(
+        static_cast<int32_t>(NfcStatus::FAILED));
+  }
+  *_aidl_return = Cf_hal_write(data.size(), &data[0]);
+  return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::setEnableVerboseLogging(bool enable) {
+  LOG(INFO) << "setVerboseLogging";
+  Cf_hal_setVerboseLogging(enable);
+  return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Nfc::isVerboseLoggingEnabled(bool* _aidl_return) {
+  *_aidl_return = Cf_hal_getVerboseLogging();
+  return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace nfc
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/guest/hals/nfc/Nfc.h b/guest/hals/nfc/Nfc.h
new file mode 100644
index 0000000..cf473be
--- /dev/null
+++ b/guest/hals/nfc/Nfc.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/nfc/BnNfc.h>
+#include <aidl/android/hardware/nfc/INfcClientCallback.h>
+#include <android-base/logging.h>
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace nfc {
+
+using ::aidl::android::hardware::nfc::NfcCloseType;
+using ::aidl::android::hardware::nfc::NfcConfig;
+using ::aidl::android::hardware::nfc::NfcStatus;
+
+// Default implementation that reports no support NFC.
+struct Nfc : public BnNfc {
+ public:
+  Nfc() = default;
+
+  ::ndk::ScopedAStatus open(
+      const std::shared_ptr<INfcClientCallback>& clientCallback) override;
+  ::ndk::ScopedAStatus close(NfcCloseType type) override;
+  ::ndk::ScopedAStatus coreInitialized() override;
+  ::ndk::ScopedAStatus factoryReset() override;
+  ::ndk::ScopedAStatus getConfig(NfcConfig* _aidl_return) override;
+  ::ndk::ScopedAStatus powerCycle() override;
+  ::ndk::ScopedAStatus preDiscover() override;
+  ::ndk::ScopedAStatus write(const std::vector<uint8_t>& data,
+                             int32_t* _aidl_return) override;
+  ::ndk::ScopedAStatus setEnableVerboseLogging(bool enable) override;
+  ::ndk::ScopedAStatus isVerboseLoggingEnabled(bool* _aidl_return) override;
+  static void eventCallback(uint8_t event, uint8_t status) {
+    if (mCallback != nullptr) {
+      auto ret = mCallback->sendEvent((NfcEvent)event, (NfcStatus)status);
+      if (!ret.isOk()) {
+        LOG(ERROR) << "Failed to send event!";
+      }
+    }
+  }
+
+  static void dataCallback(uint16_t data_len, uint8_t* p_data) {
+    std::vector<uint8_t> data(p_data, p_data + data_len);
+    if (mCallback != nullptr) {
+      auto ret = mCallback->sendData(data);
+      if (!ret.isOk()) {
+        LOG(ERROR) << "Failed to send data!";
+      }
+    }
+  }
+  static std::shared_ptr<INfcClientCallback> mCallback;
+};
+
+}  // namespace nfc
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/guest/hals/nfc/OWNERS b/guest/hals/nfc/OWNERS
new file mode 100644
index 0000000..2c46507
--- /dev/null
+++ b/guest/hals/nfc/OWNERS
@@ -0,0 +1 @@
+include platform/packages/apps/Nfc:/OWNERS
diff --git a/guest/hals/nfc/conf/Android.bp b/guest/hals/nfc/conf/Android.bp
new file mode 100644
index 0000000..cc0a2b4
--- /dev/null
+++ b/guest/hals/nfc/conf/Android.bp
@@ -0,0 +1,12 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+    name: "libnfc-hal-cf.conf-default",
+    src: "libnfc-hal-cf.conf",
+    proprietary: true,
+    vendor: true,
+    filename: "libnfc-hal-cf.conf",
+
+}
diff --git a/guest/hals/nfc/conf/libnfc-hal-cf.conf b/guest/hals/nfc/conf/libnfc-hal-cf.conf
new file mode 100644
index 0000000..e79651e
--- /dev/null
+++ b/guest/hals/nfc/conf/libnfc-hal-cf.conf
@@ -0,0 +1,120 @@
+########################### Start of libnf-hal-cf.conf ###########################
+
+###############################################################################
+###############################################################################
+# CF HAL trace log level
+CFNFC_HAL_LOGLEVEL=4
+NFC_DEBUG_ENABLED=0
+
+###############################################################################
+# File used for NFA storage
+NFA_STORAGE="/data/nfc"
+
+###############################################################################
+# Keep the nfa storage file.
+PRESERVE_STORAGE=1
+
+###############################################################################
+# Vendor Specific Proprietary Protocol & Discovery Configuration
+# Set to 0xFF if unsupported
+#  byte[0] NCI_PROTOCOL_18092_ACTIVE
+#  byte[1] NCI_PROTOCOL_B_PRIME
+#  byte[2] NCI_PROTOCOL_DUAL
+#  byte[3] NCI_PROTOCOL_15693
+#  byte[4] NCI_PROTOCOL_KOVIO
+#  byte[5] NCI_PROTOCOL_MIFARE
+#  byte[6] NCI_DISCOVERY_TYPE_POLL_KOVIO
+#  byte[7] NCI_DISCOVERY_TYPE_POLL_B_PRIME
+#  byte[8] NCI_DISCOVERY_TYPE_LISTEN_B_PRIME
+NFA_PROPRIETARY_CFG={05:FF:FF:06:8A:90:77:FF:FF}
+
+###############################################################################
+# Choose the presence-check algorithm for type-4 tag.  If not defined,
+# the default value is 1.
+# 0  NFA_RW_PRES_CHK_DEFAULT; Let stack selects an algorithm
+# 1  NFA_RW_PRES_CHK_I_BLOCK; ISO-DEP protocol's empty I-block
+# 2  NFA_RW_PRES_CHK_RESET; Deactivate to Sleep, then re-activate
+# 3  NFA_RW_PRES_CHK_RB_CH0; Type-4 tag protocol's ReadBinary command on channel 0
+# 4  NFA_RW_PRES_CHK_RB_CH3; Type-4 tag protocol's ReadBinary command on channel 3
+# 5  NFA_RW_PRES_CHK_ISO_DEP_NAK; presence check command ISO-DEP NAK as per NCI2.0
+PRESENCE_CHECK_ALGORITHM=5
+
+###############################################################################
+# Name of the NCI HAL module to use
+# If unset, falls back to nfc_nci.bcm2079x
+NCI_HAL_MODULE="nfc_nci.st21nfc"
+
+###############################################################################
+# White list to be set at startup.
+DEVICE_HOST_ALLOW_LIST={02:C0}
+
+###############################################################################
+# BAIL OUT value for P2P
+# Implements algorithm for NFC-DEP protocol priority over ISO-DEP protocol.
+POLL_BAIL_OUT_MODE=1
+
+###############################################################################
+# Extended APDU length for ISO_DEP
+ISO_DEP_MAX_TRANSCEIVE=0xFEFF
+
+###############################################################################
+# Configure the NFC Extras to open and use a static pipe.  If the value is
+# not set or set to 0, then the default is use a dynamic pipe based on a
+# destination gate (see NFA_HCI_DEFAULT_DEST_GATE).  Note there is a value
+# for each EE (ESE/SIM)
+OFF_HOST_ESE_PIPE_ID=0x5E
+OFF_HOST_SIM_PIPE_ID=0x3E
+
+###############################################################################
+#Set the default Felica T3T System Code OffHost route Location :
+#This settings will be used when application does not set this parameter
+# host  0x00
+# eSE   0x82 (eSE),    0x86 (eUICC/SPI-SE)
+# UICC  0x81 (UICC_1), 0x85 (UICC_2)
+DEFAULT_SYS_CODE_ROUTE=0x86
+
+###############################################################################
+#Set the Felica T3T System Code supported power state:
+DEFAULT_SYS_CODE_PWR_STATE=0x3B
+
+###############################################################################
+# Default off-host route for Felica.
+# This settings will be used when application does not set this parameter
+# host  0x00
+# eSE   0x82 (eSE),    0x86 (eUICC/SPI-SE)
+# UICC  0x81 (UICC_1), 0x85 (UICC_2)
+DEFAULT_NFCF_ROUTE=0x86
+
+###############################################################################
+# Configure the default off-host route.
+# used for technology A and B routing
+# eSE   0x82 (eSE),    0x86 (eUICC/SPI-SE)
+# UICC  0x81 (UICC_1), 0x85 (UICC_2)
+DEFAULT_OFFHOST_ROUTE=0x81
+
+###############################################################################
+# Configure the default AID route.
+# host  0x00
+# eSE   0x82 (eSE),    0x86 (eUICC/SPI-SE)
+# UICC  0x81 (UICC_1), 0x85 (UICC_2)
+DEFAULT_ROUTE=0x00
+
+###############################################################################
+# Configure the NFCEEIDs of offhost UICC.
+# UICC  0x81 (UICC_1), 0x85 (UICC_2)
+OFFHOST_ROUTE_UICC={81}
+
+###############################################################################
+# Configure the NFCEEIDs of offhost eSEs.
+# eSE   0x82 (eSE),    0x86 (eUICC/SPI-SE)
+OFFHOST_ROUTE_ESE={86}
+
+###############################################################################
+# Configure the list of NFCEE for the ISO-DEP routing.
+# host  0x00
+# eSE   0x82 (eSE),    0x86 (eUICC/SPI-SE)
+# UICC  0x81 (UICC_1), 0x85 (UICC_2)
+DEFAULT_ISODEP_ROUTE=0x81
+
+
+
diff --git a/guest/hals/nfc/hardware_nfc.h b/guest/hals/nfc/hardware_nfc.h
new file mode 100644
index 0000000..b266541
--- /dev/null
+++ b/guest/hals/nfc/hardware_nfc.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+typedef uint8_t nfc_event_t;
+typedef uint8_t nfc_status_t;
+
+/*
+ * The callback passed in from the NFC stack that the HAL
+ * can use to pass events back to the stack.
+ */
+typedef void(nfc_stack_callback_t)(nfc_event_t event,
+                                   nfc_status_t event_status);
+
+/*
+ * The callback passed in from the NFC stack that the HAL
+ * can use to pass incomming data to the stack.
+ */
+typedef void(nfc_stack_data_callback_t)(uint16_t data_len, uint8_t* p_data);
+
+enum {
+  HAL_NFC_OPEN_CPLT_EVT = 0u,
+  HAL_NFC_CLOSE_CPLT_EVT = 1u,
+  HAL_NFC_POST_INIT_CPLT_EVT = 2u,
+  HAL_NFC_PRE_DISCOVER_CPLT_EVT = 3u,
+  HAL_NFC_REQUEST_CONTROL_EVT = 4u,
+  HAL_NFC_RELEASE_CONTROL_EVT = 5u,
+  HAL_NFC_ERROR_EVT = 6u,
+  HAL_HCI_NETWORK_RESET = 7u,
+};
+
+enum {
+  HAL_NFC_STATUS_OK = 0u,
+  HAL_NFC_STATUS_FAILED = 1u,
+  HAL_NFC_STATUS_ERR_TRANSPORT = 2u,
+  HAL_NFC_STATUS_ERR_CMD_TIMEOUT = 3u,
+  HAL_NFC_STATUS_REFUSED = 4u,
+};
diff --git a/guest/hals/nfc/main.cc b/guest/hals/nfc/main.cc
new file mode 100644
index 0000000..51ec8ce
--- /dev/null
+++ b/guest/hals/nfc/main.cc
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "Nfc.h"
+using ::aidl::android::hardware::nfc::Nfc;
+
+int main() {
+  LOG(INFO) << "NFC HAL starting up";
+  if (!ABinderProcess_setThreadPoolMaxThreadCount(1)) {
+    LOG(INFO) << "failed to set thread pool max thread count";
+    return 1;
+  }
+  std::shared_ptr<Nfc> nfc_service = ndk::SharedRefBase::make<Nfc>();
+
+  const std::string instance = std::string() + Nfc::descriptor + "/default";
+  CHECK_EQ(STATUS_OK, AServiceManager_addService(nfc_service->asBinder().get(),
+                                                 instance.c_str()));
+  ABinderProcess_joinThreadPool();
+  return 0;
+}
diff --git a/guest/hals/nfc/nfc-service-cuttlefish.rc b/guest/hals/nfc/nfc-service-cuttlefish.rc
new file mode 100644
index 0000000..e8f6505
--- /dev/null
+++ b/guest/hals/nfc/nfc-service-cuttlefish.rc
@@ -0,0 +1,4 @@
+service nfc_hal_service /vendor/bin/hw/android.hardware.nfc-service.cuttlefish
+    class hal
+    user nfc
+    group nfc
diff --git a/guest/hals/nfc/nfc-service-cuttlefish.xml b/guest/hals/nfc/nfc-service-cuttlefish.xml
new file mode 100644
index 0000000..70fed20
--- /dev/null
+++ b/guest/hals/nfc/nfc-service-cuttlefish.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.nfc</name>
+        <fqname>INfc/default</fqname>
+    </hal>
+</manifest>
diff --git a/guest/hals/ril/reference-libril/Android.bp b/guest/hals/ril/reference-libril/Android.bp
index 687f95f..ab5ac03 100644
--- a/guest/hals/ril/reference-libril/Android.bp
+++ b/guest/hals/ril/reference-libril/Android.bp
@@ -24,6 +24,7 @@
         "-Wno-unused-parameter",
     ],
     srcs: [
+        "RefRadioNetwork.cpp",
         "ril.cpp",
         "RilSapSocket.cpp",
         "ril_config.cpp",
@@ -36,6 +37,14 @@
         "hardware/ril/include",
     ],
     shared_libs: [
+        "android.hardware.radio-library.compat",
+        "android.hardware.radio.config-V1-ndk",
+        "android.hardware.radio.data-V1-ndk",
+        "android.hardware.radio.messaging-V1-ndk",
+        "android.hardware.radio.modem-V1-ndk",
+        "android.hardware.radio.network-V1-ndk",
+        "android.hardware.radio.sim-V1-ndk",
+        "android.hardware.radio.voice-V1-ndk",
         "android.hardware.radio@1.0",
         "android.hardware.radio@1.1",
         "android.hardware.radio@1.2",
@@ -48,6 +57,8 @@
         "android.hardware.radio.config@1.2",
         "android.hardware.radio.config@1.3",
         "android.hardware.radio.deprecated@1.0",
+        "libbase",
+        "libbinder_ndk",
         "libcutils",
         "libhardware_legacy",
         "libhidlbase",
@@ -56,15 +67,13 @@
         "libutils",
     ],
     static_libs: [
-        "libprotobuf-c-nano-enable_malloc"
+        "libprotobuf-c-nano-enable_malloc",
     ],
 }
 
 filegroup {
     name: "libril-modem-lib-manifests",
     srcs: [
-        "android.hardware.radio@1.6.xml",
-        "android.hardware.radio.config@1.3.xml",
+        "android.hardware.radio@2.0.xml",
     ],
 }
-
diff --git a/guest/hals/ril/reference-libril/RefRadioNetwork.cpp b/guest/hals/ril/reference-libril/RefRadioNetwork.cpp
new file mode 100644
index 0000000..4f39944
--- /dev/null
+++ b/guest/hals/ril/reference-libril/RefRadioNetwork.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#include "RefRadioNetwork.h"
+
+namespace cf::ril {
+
+using ::ndk::ScopedAStatus;
+using namespace ::aidl::android::hardware::radio;
+constexpr auto ok = &ScopedAStatus::ok;
+
+static RadioResponseInfo responseInfo(int32_t serial, RadioError error = RadioError::NONE) {
+    return {
+            .type = RadioResponseType::SOLICITED,
+            .serial = serial,
+            .error = error,
+    };
+}
+
+ScopedAStatus RefRadioNetwork::setUsageSetting(int32_t serial, network::UsageSetting usageSetting) {
+    if (usageSetting != network::UsageSetting::VOICE_CENTRIC &&
+        usageSetting != network::UsageSetting::DATA_CENTRIC) {
+        respond()->setUsageSettingResponse(responseInfo(serial, RadioError::INVALID_ARGUMENTS));
+        return ok();
+    }
+
+    mUsageSetting = usageSetting;
+    respond()->setUsageSettingResponse(responseInfo(serial));
+    return ok();
+}
+
+ScopedAStatus RefRadioNetwork::getUsageSetting(int32_t serial) {
+    respond()->getUsageSettingResponse(responseInfo(serial), mUsageSetting);
+    return ok();
+}
+
+}  // namespace cf::ril
diff --git a/guest/hals/ril/reference-libril/RefRadioNetwork.h b/guest/hals/ril/reference-libril/RefRadioNetwork.h
new file mode 100644
index 0000000..5f16b14
--- /dev/null
+++ b/guest/hals/ril/reference-libril/RefRadioNetwork.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <libradiocompat/RadioNetwork.h>
+
+namespace cf::ril {
+
+class RefRadioNetwork : public android::hardware::radio::compat::RadioNetwork {
+    ::aidl::android::hardware::radio::network::UsageSetting mUsageSetting =
+            ::aidl::android::hardware::radio::network::UsageSetting::VOICE_CENTRIC;
+
+  public:
+    using android::hardware::radio::compat::RadioNetwork::RadioNetwork;
+
+    ::ndk::ScopedAStatus setUsageSetting(
+            int32_t serial,
+            ::aidl::android::hardware::radio::network::UsageSetting usageSetting) override;
+    ::ndk::ScopedAStatus getUsageSetting(int32_t serial) override;
+};
+
+}  // namespace cf::ril
diff --git a/guest/hals/ril/reference-libril/android.hardware.radio@1.6.xml b/guest/hals/ril/reference-libril/android.hardware.radio@1.6.xml
deleted file mode 100644
index 8652229..0000000
--- a/guest/hals/ril/reference-libril/android.hardware.radio@1.6.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<manifest version="1.0" type="device">
-    <hal format="hidl">
-        <name>android.hardware.radio</name>
-        <transport>hwbinder</transport>
-        <version>1.6</version>
-        <interface>
-            <name>IRadio</name>
-            <instance>slot1</instance>
-            <!-- cuttlefish doesn't support SIM slot 2/3 -->
-        </interface>
-        <!-- TODO (b/130079344):
-        <interface>
-            <name>ISap</name>
-            <instance>slot1</instance>
-        </interface>
-        -->
-    </hal>
-</manifest>
diff --git a/guest/hals/ril/reference-libril/android.hardware.radio@2.0.xml b/guest/hals/ril/reference-libril/android.hardware.radio@2.0.xml
new file mode 100644
index 0000000..8501dec
--- /dev/null
+++ b/guest/hals/ril/reference-libril/android.hardware.radio@2.0.xml
@@ -0,0 +1,30 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.radio.config</name>
+        <fqname>IRadioConfig/default</fqname>
+    </hal>
+    <hal format="aidl">
+        <name>android.hardware.radio.data</name>
+        <fqname>IRadioData/slot1</fqname>
+    </hal>
+    <hal format="aidl">
+        <name>android.hardware.radio.messaging</name>
+        <fqname>IRadioMessaging/slot1</fqname>
+    </hal>
+    <hal format="aidl">
+        <name>android.hardware.radio.modem</name>
+        <fqname>IRadioModem/slot1</fqname>
+    </hal>
+    <hal format="aidl">
+        <name>android.hardware.radio.network</name>
+        <fqname>IRadioNetwork/slot1</fqname>
+    </hal>
+    <hal format="aidl">
+        <name>android.hardware.radio.sim</name>
+        <fqname>IRadioSim/slot1</fqname>
+    </hal>
+    <hal format="aidl">
+        <name>android.hardware.radio.voice</name>
+        <fqname>IRadioVoice/slot1</fqname>
+    </hal>
+</manifest>
diff --git a/guest/hals/ril/reference-libril/ril.h b/guest/hals/ril/reference-libril/ril.h
index 586de42..f7bc2c5 100644
--- a/guest/hals/ril/reference-libril/ril.h
+++ b/guest/hals/ril/reference-libril/ril.h
@@ -6202,7 +6202,7 @@
  *
  * "data" is NULL
  *
- * "response" is an array of  RIL_CellInfo_v12.
+ * "response" is an array of RIL_CellInfo_v12.
  *
  * Valid errors:
  *  SUCCESS
@@ -7543,8 +7543,12 @@
 
 #define RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORDS 172
 
+/**
+ * Same as RIL_REQUEST_GET_CELL_INFO_LIST but "response" is an array of RIL_CellInfo_v16.
+ */
+#define RIL_REQUEST_GET_CELL_INFO_LIST_1_6 173
 
-#define RIL_REQUEST_LAST RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORDS
+#define RIL_REQUEST_LAST RIL_REQUEST_GET_CELL_INFO_LIST_1_6
 
 /***********************************************************************/
 
diff --git a/guest/hals/ril/reference-libril/ril_commands.h b/guest/hals/ril/reference-libril/ril_commands.h
index a100b48..4b1c48d 100644
--- a/guest/hals/ril/reference-libril/ril_commands.h
+++ b/guest/hals/ril/reference-libril/ril_commands.h
@@ -14,176 +14,193 @@
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
-    {0, NULL},                   //none
-    {RIL_REQUEST_GET_SIM_STATUS, radio_1_6::getIccCardStatusResponse},
-    {RIL_REQUEST_ENTER_SIM_PIN, radio_1_6::supplyIccPinForAppResponse},
-    {RIL_REQUEST_ENTER_SIM_PUK, radio_1_6::supplyIccPukForAppResponse},
-    {RIL_REQUEST_ENTER_SIM_PIN2, radio_1_6::supplyIccPin2ForAppResponse},
-    {RIL_REQUEST_ENTER_SIM_PUK2, radio_1_6::supplyIccPuk2ForAppResponse},
-    {RIL_REQUEST_CHANGE_SIM_PIN, radio_1_6::changeIccPinForAppResponse},
-    {RIL_REQUEST_CHANGE_SIM_PIN2, radio_1_6::changeIccPin2ForAppResponse},
-    {RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION, radio_1_6::supplyNetworkDepersonalizationResponse},
-    {RIL_REQUEST_GET_CURRENT_CALLS, radio_1_6::getCurrentCallsResponse},
-    {RIL_REQUEST_DIAL, radio_1_6::dialResponse},
-    {RIL_REQUEST_GET_IMSI, radio_1_6::getIMSIForAppResponse},
-    {RIL_REQUEST_HANGUP, radio_1_6::hangupConnectionResponse},
-    {RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, radio_1_6::hangupWaitingOrBackgroundResponse},
-    {RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND, radio_1_6::hangupForegroundResumeBackgroundResponse},
-    {RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, radio_1_6::switchWaitingOrHoldingAndActiveResponse},
-    {RIL_REQUEST_CONFERENCE, radio_1_6::conferenceResponse},
-    {RIL_REQUEST_UDUB, radio_1_6::rejectCallResponse},
-    {RIL_REQUEST_LAST_CALL_FAIL_CAUSE, radio_1_6::getLastCallFailCauseResponse},
-    {RIL_REQUEST_SIGNAL_STRENGTH, radio_1_6::getSignalStrengthResponse},
-    {RIL_REQUEST_VOICE_REGISTRATION_STATE, radio_1_6::getVoiceRegistrationStateResponse},
-    {RIL_REQUEST_DATA_REGISTRATION_STATE, radio_1_6::getDataRegistrationStateResponse},
-    {RIL_REQUEST_OPERATOR, radio_1_6::getOperatorResponse},
-    {RIL_REQUEST_RADIO_POWER, radio_1_6::setRadioPowerResponse},
-    {RIL_REQUEST_DTMF, radio_1_6::sendDtmfResponse},
-    {RIL_REQUEST_SEND_SMS, radio_1_6::sendSmsResponse},
-    {RIL_REQUEST_SEND_SMS_EXPECT_MORE, radio_1_6::sendSmsExpectMoreResponse},
-    {RIL_REQUEST_SETUP_DATA_CALL, radio_1_6::setupDataCallResponse},
-    {RIL_REQUEST_SIM_IO, radio_1_6::iccIOForAppResponse},
-    {RIL_REQUEST_SEND_USSD, radio_1_6::sendUssdResponse},
-    {RIL_REQUEST_CANCEL_USSD, radio_1_6::cancelPendingUssdResponse},
-    {RIL_REQUEST_GET_CLIR, radio_1_6::getClirResponse},
-    {RIL_REQUEST_SET_CLIR, radio_1_6::setClirResponse},
-    {RIL_REQUEST_QUERY_CALL_FORWARD_STATUS, radio_1_6::getCallForwardStatusResponse},
-    {RIL_REQUEST_SET_CALL_FORWARD, radio_1_6::setCallForwardResponse},
-    {RIL_REQUEST_QUERY_CALL_WAITING, radio_1_6::getCallWaitingResponse},
-    {RIL_REQUEST_SET_CALL_WAITING, radio_1_6::setCallWaitingResponse},
-    {RIL_REQUEST_SMS_ACKNOWLEDGE, radio_1_6::acknowledgeLastIncomingGsmSmsResponse},
-    {RIL_REQUEST_GET_IMEI, NULL},
-    {RIL_REQUEST_GET_IMEISV, NULL},
-    {RIL_REQUEST_ANSWER, radio_1_6::acceptCallResponse},
-    {RIL_REQUEST_DEACTIVATE_DATA_CALL, radio_1_6::deactivateDataCallResponse},
-    {RIL_REQUEST_QUERY_FACILITY_LOCK, radio_1_6::getFacilityLockForAppResponse},
-    {RIL_REQUEST_SET_FACILITY_LOCK, radio_1_6::setFacilityLockForAppResponse},
-    {RIL_REQUEST_CHANGE_BARRING_PASSWORD, radio_1_6::setBarringPasswordResponse},
-    {RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE, radio_1_6::getNetworkSelectionModeResponse},
-    {RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, radio_1_6::setNetworkSelectionModeAutomaticResponse},
-    {RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL, radio_1_6::setNetworkSelectionModeManualResponse},
-    {RIL_REQUEST_QUERY_AVAILABLE_NETWORKS , radio_1_6::getAvailableNetworksResponse},
-    {RIL_REQUEST_DTMF_START, radio_1_6::startDtmfResponse},
-    {RIL_REQUEST_DTMF_STOP, radio_1_6::stopDtmfResponse},
-    {RIL_REQUEST_BASEBAND_VERSION, radio_1_6::getBasebandVersionResponse},
-    {RIL_REQUEST_SEPARATE_CONNECTION, radio_1_6::separateConnectionResponse},
-    {RIL_REQUEST_SET_MUTE, radio_1_6::setMuteResponse},
-    {RIL_REQUEST_GET_MUTE, radio_1_6::getMuteResponse},
-    {RIL_REQUEST_QUERY_CLIP, radio_1_6::getClipResponse},
-    {RIL_REQUEST_LAST_DATA_CALL_FAIL_CAUSE, NULL},
-    {RIL_REQUEST_DATA_CALL_LIST, radio_1_6::getDataCallListResponse},
-    {RIL_REQUEST_RESET_RADIO, NULL},
-    {RIL_REQUEST_OEM_HOOK_RAW, radio_1_6::sendRequestRawResponse},
-    {RIL_REQUEST_OEM_HOOK_STRINGS, radio_1_6::sendRequestStringsResponse},
-    {RIL_REQUEST_SCREEN_STATE, radio_1_6::sendDeviceStateResponse},   // Note the response function is different.
-    {RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION, radio_1_6::setSuppServiceNotificationsResponse},
-    {RIL_REQUEST_WRITE_SMS_TO_SIM, radio_1_6::writeSmsToSimResponse},
-    {RIL_REQUEST_DELETE_SMS_ON_SIM, radio_1_6::deleteSmsOnSimResponse},
-    {RIL_REQUEST_SET_BAND_MODE, radio_1_6::setBandModeResponse},
-    {RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE, radio_1_6::getAvailableBandModesResponse},
-    {RIL_REQUEST_STK_GET_PROFILE, NULL},
-    {RIL_REQUEST_STK_SET_PROFILE, NULL},
-    {RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND, radio_1_6::sendEnvelopeResponse},
-    {RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE, radio_1_6::sendTerminalResponseToSimResponse},
-    {RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM, radio_1_6::handleStkCallSetupRequestFromSimResponse},
-    {RIL_REQUEST_EXPLICIT_CALL_TRANSFER, radio_1_6::explicitCallTransferResponse},
-    {RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE, radio_1_6::setPreferredNetworkTypeResponse},
-    {RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE, radio_1_6::getPreferredNetworkTypeResponse},
-    {RIL_REQUEST_GET_NEIGHBORING_CELL_IDS, radio_1_6::getNeighboringCidsResponse},
-    {RIL_REQUEST_SET_LOCATION_UPDATES, radio_1_6::setLocationUpdatesResponse},
-    {RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE, radio_1_6::setCdmaSubscriptionSourceResponse},
-    {RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE, radio_1_6::setCdmaRoamingPreferenceResponse},
-    {RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, radio_1_6::getCdmaRoamingPreferenceResponse},
-    {RIL_REQUEST_SET_TTY_MODE, radio_1_6::setTTYModeResponse},
-    {RIL_REQUEST_QUERY_TTY_MODE, radio_1_6::getTTYModeResponse},
-    {RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, radio_1_6::setPreferredVoicePrivacyResponse},
-    {RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE, radio_1_6::getPreferredVoicePrivacyResponse},
-    {RIL_REQUEST_CDMA_FLASH, radio_1_6::sendCDMAFeatureCodeResponse},
-    {RIL_REQUEST_CDMA_BURST_DTMF, radio_1_6::sendBurstDtmfResponse},
-    {RIL_REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY, NULL},
-    {RIL_REQUEST_CDMA_SEND_SMS, radio_1_6::sendCdmaSmsResponse},
-    {RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE, radio_1_6::acknowledgeLastIncomingCdmaSmsResponse},
-    {RIL_REQUEST_GSM_GET_BROADCAST_SMS_CONFIG, radio_1_6::getGsmBroadcastConfigResponse},
-    {RIL_REQUEST_GSM_SET_BROADCAST_SMS_CONFIG, radio_1_6::setGsmBroadcastConfigResponse},
-    {RIL_REQUEST_GSM_SMS_BROADCAST_ACTIVATION, radio_1_6::setGsmBroadcastActivationResponse},
-    {RIL_REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG, radio_1_6::getCdmaBroadcastConfigResponse},
-    {RIL_REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG, radio_1_6::setCdmaBroadcastConfigResponse},
-    {RIL_REQUEST_CDMA_SMS_BROADCAST_ACTIVATION, radio_1_6::setCdmaBroadcastActivationResponse},
-    {RIL_REQUEST_CDMA_SUBSCRIPTION, radio_1_6::getCDMASubscriptionResponse},
-    {RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM, radio_1_6::writeSmsToRuimResponse},
-    {RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM, radio_1_6::deleteSmsOnRuimResponse},
-    {RIL_REQUEST_DEVICE_IDENTITY, radio_1_6::getDeviceIdentityResponse},
-    {RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, radio_1_6::exitEmergencyCallbackModeResponse},
-    {RIL_REQUEST_GET_SMSC_ADDRESS, radio_1_6::getSmscAddressResponse},
-    {RIL_REQUEST_SET_SMSC_ADDRESS, radio_1_6::setSmscAddressResponse},
-    {RIL_REQUEST_REPORT_SMS_MEMORY_STATUS, radio_1_6::reportSmsMemoryStatusResponse},
-    {RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING, radio_1_6::reportStkServiceIsRunningResponse},
-    {RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE, radio_1_6::getCdmaSubscriptionSourceResponse},
-    {RIL_REQUEST_ISIM_AUTHENTICATION, radio_1_6::requestIsimAuthenticationResponse},
-    {RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU, radio_1_6::acknowledgeIncomingGsmSmsWithPduResponse},
-    {RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, radio_1_6::sendEnvelopeWithStatusResponse},
-    {RIL_REQUEST_VOICE_RADIO_TECH, radio_1_6::getVoiceRadioTechnologyResponse},
-    {RIL_REQUEST_GET_CELL_INFO_LIST, radio_1_6::getCellInfoListResponse},
-    {RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE, radio_1_6::setCellInfoListRateResponse},
-    {RIL_REQUEST_SET_INITIAL_ATTACH_APN, radio_1_6::setInitialAttachApnResponse},
-    {RIL_REQUEST_IMS_REGISTRATION_STATE, radio_1_6::getImsRegistrationStateResponse},
-    {RIL_REQUEST_IMS_SEND_SMS, radio_1_6::sendImsSmsResponse},
-    {RIL_REQUEST_SIM_TRANSMIT_APDU_BASIC, radio_1_6::iccTransmitApduBasicChannelResponse},
-    {RIL_REQUEST_SIM_OPEN_CHANNEL, radio_1_6::iccOpenLogicalChannelResponse},
-    {RIL_REQUEST_SIM_CLOSE_CHANNEL, radio_1_6::iccCloseLogicalChannelResponse},
-    {RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL, radio_1_6::iccTransmitApduLogicalChannelResponse},
-    {RIL_REQUEST_NV_READ_ITEM, radio_1_6::nvReadItemResponse},
-    {RIL_REQUEST_NV_WRITE_ITEM, radio_1_6::nvWriteItemResponse},
-    {RIL_REQUEST_NV_WRITE_CDMA_PRL, radio_1_6::nvWriteCdmaPrlResponse},
-    {RIL_REQUEST_NV_RESET_CONFIG, radio_1_6::nvResetConfigResponse},
-    {RIL_REQUEST_SET_UICC_SUBSCRIPTION, radio_1_6::setUiccSubscriptionResponse},
-    {RIL_REQUEST_ALLOW_DATA, radio_1_6::setDataAllowedResponse},
-    {RIL_REQUEST_GET_HARDWARE_CONFIG, radio_1_6::getHardwareConfigResponse},
-    {RIL_REQUEST_SIM_AUTHENTICATION, radio_1_6::requestIccSimAuthenticationResponse},
-    {RIL_REQUEST_GET_DC_RT_INFO, NULL},
-    {RIL_REQUEST_SET_DC_RT_INFO_RATE, NULL},
-    {RIL_REQUEST_SET_DATA_PROFILE, radio_1_6::setDataProfileResponse},
-    {RIL_REQUEST_SHUTDOWN, radio_1_6::requestShutdownResponse},
-    {RIL_REQUEST_GET_RADIO_CAPABILITY, radio_1_6::getRadioCapabilityResponse},
-    {RIL_REQUEST_SET_RADIO_CAPABILITY, radio_1_6::setRadioCapabilityResponse},
-    {RIL_REQUEST_START_LCE, radio_1_6::startLceServiceResponse},
-    {RIL_REQUEST_STOP_LCE, radio_1_6::stopLceServiceResponse},
-    {RIL_REQUEST_PULL_LCEDATA, radio_1_6::pullLceDataResponse},
-    {RIL_REQUEST_GET_ACTIVITY_INFO, radio_1_6::getModemActivityInfoResponse},
-    {RIL_REQUEST_SET_CARRIER_RESTRICTIONS, radio_1_6::setAllowedCarriersResponse},
-    {RIL_REQUEST_GET_CARRIER_RESTRICTIONS, radio_1_6::getAllowedCarriersResponse},
-    {RIL_REQUEST_SEND_DEVICE_STATE, radio_1_6::sendDeviceStateResponse},
-    {RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER, radio_1_6::setIndicationFilterResponse},
-    {RIL_REQUEST_SET_SIM_CARD_POWER, radio_1_6::setSimCardPowerResponse},
-    {RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION, radio_1_6::setCarrierInfoForImsiEncryptionResponse},
-    {RIL_REQUEST_START_NETWORK_SCAN, radio_1_6::startNetworkScanResponse},
-    {RIL_REQUEST_STOP_NETWORK_SCAN, radio_1_6::stopNetworkScanResponse},
-    {RIL_REQUEST_START_KEEPALIVE, radio_1_6::startKeepaliveResponse},
-    {RIL_REQUEST_STOP_KEEPALIVE, radio_1_6::stopKeepaliveResponse},
-    {RIL_REQUEST_GET_MODEM_STACK_STATUS, radio_1_6::getModemStackStatusResponse},
-    {RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE_BITMAP, radio_1_6::getPreferredNetworkTypeBitmapResponse},
-    {RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE_BITMAP, radio_1_6::setPreferredNetworkTypeBitmapResponse},
-    {RIL_REQUEST_EMERGENCY_DIAL, radio_1_6::emergencyDialResponse},
-    {RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS, radio_1_6::setSystemSelectionChannelsResponse},
-    {RIL_REQUEST_ENABLE_MODEM, radio_1_6::enableModemResponse},
-    {RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA, radio_1_6::setSignalStrengthReportingCriteriaResponse},
-    {RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA, radio_1_6::setLinkCapacityReportingCriteriaResponse},
-    {RIL_REQUEST_ENABLE_UICC_APPLICATIONS, radio_1_6::enableUiccApplicationsResponse},
-    {RIL_REQUEST_ARE_UICC_APPLICATIONS_ENABLED, radio_1_6::areUiccApplicationsEnabledResponse},
-    {RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION, radio_1_6::supplySimDepersonalizationResponse},
-    {RIL_REQUEST_CDMA_SEND_SMS_EXPECT_MORE, radio_1_6::sendCdmaSmsExpectMoreResponse},
-    {RIL_REQUEST_GET_BARRING_INFO, radio_1_6::getBarringInfoResponse},
-    {RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY, radio_1_6::setNrDualConnectivityStateResponse},
-    {RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED, radio_1_6::isNrDualConnectivityEnabledResponse},
-    {RIL_REQUEST_ALLOCATE_PDU_SESSION_ID, radio_1_6::allocatePduSessionIdResponse},
-    {RIL_REQUEST_RELEASE_PDU_SESSION_ID, radio_1_6::releasePduSessionIdResponse},
-    {RIL_REQUEST_START_HANDOVER, radio_1_6::startHandoverResponse},
-    {RIL_REQUEST_CANCEL_HANDOVER, radio_1_6::cancelHandoverResponse},
-    {RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP, radio_1_6::setAllowedNetworkTypesBitmapResponse},
-    {RIL_REQUEST_SET_DATA_THROTTLING, radio_1_6::setDataThrottlingResponse},
-    {RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS, radio_1_6::getSystemSelectionChannelsResponse},
-    {RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP, radio_1_6::getAllowedNetworkTypesBitmapResponse},
-    {RIL_REQUEST_GET_SLICING_CONFIG, radio_1_6::getSlicingConfigResponse},
-    {RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS, radio_1_6::getSimPhonebookRecordsResponse},
-    {RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY, radio_1_6::getSimPhonebookCapacityResponse},
-    {RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORDS, radio_1_6::updateSimPhonebookRecordsResponse}
+{0, NULL},  // none
+        {RIL_REQUEST_GET_SIM_STATUS, radio_1_6::getIccCardStatusResponse},
+        {RIL_REQUEST_ENTER_SIM_PIN, radio_1_6::supplyIccPinForAppResponse},
+        {RIL_REQUEST_ENTER_SIM_PUK, radio_1_6::supplyIccPukForAppResponse},
+        {RIL_REQUEST_ENTER_SIM_PIN2, radio_1_6::supplyIccPin2ForAppResponse},
+        {RIL_REQUEST_ENTER_SIM_PUK2, radio_1_6::supplyIccPuk2ForAppResponse},
+        {RIL_REQUEST_CHANGE_SIM_PIN, radio_1_6::changeIccPinForAppResponse},
+        {RIL_REQUEST_CHANGE_SIM_PIN2, radio_1_6::changeIccPin2ForAppResponse},
+        {RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION,
+         radio_1_6::supplyNetworkDepersonalizationResponse},
+        {RIL_REQUEST_GET_CURRENT_CALLS, radio_1_6::getCurrentCallsResponse},
+        {RIL_REQUEST_DIAL, radio_1_6::dialResponse},
+        {RIL_REQUEST_GET_IMSI, radio_1_6::getIMSIForAppResponse},
+        {RIL_REQUEST_HANGUP, radio_1_6::hangupConnectionResponse},
+        {RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, radio_1_6::hangupWaitingOrBackgroundResponse},
+        {RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND,
+         radio_1_6::hangupForegroundResumeBackgroundResponse},
+        {RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE,
+         radio_1_6::switchWaitingOrHoldingAndActiveResponse},
+        {RIL_REQUEST_CONFERENCE, radio_1_6::conferenceResponse},
+        {RIL_REQUEST_UDUB, radio_1_6::rejectCallResponse},
+        {RIL_REQUEST_LAST_CALL_FAIL_CAUSE, radio_1_6::getLastCallFailCauseResponse},
+        {RIL_REQUEST_SIGNAL_STRENGTH, radio_1_6::getSignalStrengthResponse},
+        {RIL_REQUEST_VOICE_REGISTRATION_STATE, radio_1_6::getVoiceRegistrationStateResponse},
+        {RIL_REQUEST_DATA_REGISTRATION_STATE, radio_1_6::getDataRegistrationStateResponse},
+        {RIL_REQUEST_OPERATOR, radio_1_6::getOperatorResponse},
+        {RIL_REQUEST_RADIO_POWER, radio_1_6::setRadioPowerResponse},
+        {RIL_REQUEST_DTMF, radio_1_6::sendDtmfResponse},
+        {RIL_REQUEST_SEND_SMS, radio_1_6::sendSmsResponse},
+        {RIL_REQUEST_SEND_SMS_EXPECT_MORE, radio_1_6::sendSmsExpectMoreResponse},
+        {RIL_REQUEST_SETUP_DATA_CALL, radio_1_6::setupDataCallResponse},
+        {RIL_REQUEST_SIM_IO, radio_1_6::iccIOForAppResponse},
+        {RIL_REQUEST_SEND_USSD, radio_1_6::sendUssdResponse},
+        {RIL_REQUEST_CANCEL_USSD, radio_1_6::cancelPendingUssdResponse},
+        {RIL_REQUEST_GET_CLIR, radio_1_6::getClirResponse},
+        {RIL_REQUEST_SET_CLIR, radio_1_6::setClirResponse},
+        {RIL_REQUEST_QUERY_CALL_FORWARD_STATUS, radio_1_6::getCallForwardStatusResponse},
+        {RIL_REQUEST_SET_CALL_FORWARD, radio_1_6::setCallForwardResponse},
+        {RIL_REQUEST_QUERY_CALL_WAITING, radio_1_6::getCallWaitingResponse},
+        {RIL_REQUEST_SET_CALL_WAITING, radio_1_6::setCallWaitingResponse},
+        {RIL_REQUEST_SMS_ACKNOWLEDGE, radio_1_6::acknowledgeLastIncomingGsmSmsResponse},
+        {RIL_REQUEST_GET_IMEI, NULL}, {RIL_REQUEST_GET_IMEISV, NULL},
+        {RIL_REQUEST_ANSWER, radio_1_6::acceptCallResponse},
+        {RIL_REQUEST_DEACTIVATE_DATA_CALL, radio_1_6::deactivateDataCallResponse},
+        {RIL_REQUEST_QUERY_FACILITY_LOCK, radio_1_6::getFacilityLockForAppResponse},
+        {RIL_REQUEST_SET_FACILITY_LOCK, radio_1_6::setFacilityLockForAppResponse},
+        {RIL_REQUEST_CHANGE_BARRING_PASSWORD, radio_1_6::setBarringPasswordResponse},
+        {RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE, radio_1_6::getNetworkSelectionModeResponse},
+        {RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC,
+         radio_1_6::setNetworkSelectionModeAutomaticResponse},
+        {RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL,
+         radio_1_6::setNetworkSelectionModeManualResponse},
+        {RIL_REQUEST_QUERY_AVAILABLE_NETWORKS, radio_1_6::getAvailableNetworksResponse},
+        {RIL_REQUEST_DTMF_START, radio_1_6::startDtmfResponse},
+        {RIL_REQUEST_DTMF_STOP, radio_1_6::stopDtmfResponse},
+        {RIL_REQUEST_BASEBAND_VERSION, radio_1_6::getBasebandVersionResponse},
+        {RIL_REQUEST_SEPARATE_CONNECTION, radio_1_6::separateConnectionResponse},
+        {RIL_REQUEST_SET_MUTE, radio_1_6::setMuteResponse},
+        {RIL_REQUEST_GET_MUTE, radio_1_6::getMuteResponse},
+        {RIL_REQUEST_QUERY_CLIP, radio_1_6::getClipResponse},
+        {RIL_REQUEST_LAST_DATA_CALL_FAIL_CAUSE, NULL},
+        {RIL_REQUEST_DATA_CALL_LIST, radio_1_6::getDataCallListResponse},
+        {RIL_REQUEST_RESET_RADIO, NULL},
+        {RIL_REQUEST_OEM_HOOK_RAW, radio_1_6::sendRequestRawResponse},
+        {RIL_REQUEST_OEM_HOOK_STRINGS, radio_1_6::sendRequestStringsResponse},
+        {RIL_REQUEST_SCREEN_STATE,
+         radio_1_6::sendDeviceStateResponse},  // Note the response function is different.
+        {RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION, radio_1_6::setSuppServiceNotificationsResponse},
+        {RIL_REQUEST_WRITE_SMS_TO_SIM, radio_1_6::writeSmsToSimResponse},
+        {RIL_REQUEST_DELETE_SMS_ON_SIM, radio_1_6::deleteSmsOnSimResponse},
+        {RIL_REQUEST_SET_BAND_MODE, radio_1_6::setBandModeResponse},
+        {RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE, radio_1_6::getAvailableBandModesResponse},
+        {RIL_REQUEST_STK_GET_PROFILE, NULL}, {RIL_REQUEST_STK_SET_PROFILE, NULL},
+        {RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND, radio_1_6::sendEnvelopeResponse},
+        {RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE, radio_1_6::sendTerminalResponseToSimResponse},
+        {RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM,
+         radio_1_6::handleStkCallSetupRequestFromSimResponse},
+        {RIL_REQUEST_EXPLICIT_CALL_TRANSFER, radio_1_6::explicitCallTransferResponse},
+        {RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE, radio_1_6::setPreferredNetworkTypeResponse},
+        {RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE, radio_1_6::getPreferredNetworkTypeResponse},
+        {RIL_REQUEST_GET_NEIGHBORING_CELL_IDS, radio_1_6::getNeighboringCidsResponse},
+        {RIL_REQUEST_SET_LOCATION_UPDATES, radio_1_6::setLocationUpdatesResponse},
+        {RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE, radio_1_6::setCdmaSubscriptionSourceResponse},
+        {RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE, radio_1_6::setCdmaRoamingPreferenceResponse},
+        {RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, radio_1_6::getCdmaRoamingPreferenceResponse},
+        {RIL_REQUEST_SET_TTY_MODE, radio_1_6::setTTYModeResponse},
+        {RIL_REQUEST_QUERY_TTY_MODE, radio_1_6::getTTYModeResponse},
+        {RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE,
+         radio_1_6::setPreferredVoicePrivacyResponse},
+        {RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE,
+         radio_1_6::getPreferredVoicePrivacyResponse},
+        {RIL_REQUEST_CDMA_FLASH, radio_1_6::sendCDMAFeatureCodeResponse},
+        {RIL_REQUEST_CDMA_BURST_DTMF, radio_1_6::sendBurstDtmfResponse},
+        {RIL_REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY, NULL},
+        {RIL_REQUEST_CDMA_SEND_SMS, radio_1_6::sendCdmaSmsResponse},
+        {RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE, radio_1_6::acknowledgeLastIncomingCdmaSmsResponse},
+        {RIL_REQUEST_GSM_GET_BROADCAST_SMS_CONFIG, radio_1_6::getGsmBroadcastConfigResponse},
+        {RIL_REQUEST_GSM_SET_BROADCAST_SMS_CONFIG, radio_1_6::setGsmBroadcastConfigResponse},
+        {RIL_REQUEST_GSM_SMS_BROADCAST_ACTIVATION, radio_1_6::setGsmBroadcastActivationResponse},
+        {RIL_REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG, radio_1_6::getCdmaBroadcastConfigResponse},
+        {RIL_REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG, radio_1_6::setCdmaBroadcastConfigResponse},
+        {RIL_REQUEST_CDMA_SMS_BROADCAST_ACTIVATION, radio_1_6::setCdmaBroadcastActivationResponse},
+        {RIL_REQUEST_CDMA_SUBSCRIPTION, radio_1_6::getCDMASubscriptionResponse},
+        {RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM, radio_1_6::writeSmsToRuimResponse},
+        {RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM, radio_1_6::deleteSmsOnRuimResponse},
+        {RIL_REQUEST_DEVICE_IDENTITY, radio_1_6::getDeviceIdentityResponse},
+        {RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, radio_1_6::exitEmergencyCallbackModeResponse},
+        {RIL_REQUEST_GET_SMSC_ADDRESS, radio_1_6::getSmscAddressResponse},
+        {RIL_REQUEST_SET_SMSC_ADDRESS, radio_1_6::setSmscAddressResponse},
+        {RIL_REQUEST_REPORT_SMS_MEMORY_STATUS, radio_1_6::reportSmsMemoryStatusResponse},
+        {RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING, radio_1_6::reportStkServiceIsRunningResponse},
+        {RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE, radio_1_6::getCdmaSubscriptionSourceResponse},
+        {RIL_REQUEST_ISIM_AUTHENTICATION, radio_1_6::requestIsimAuthenticationResponse},
+        {RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU,
+         radio_1_6::acknowledgeIncomingGsmSmsWithPduResponse},
+        {RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, radio_1_6::sendEnvelopeWithStatusResponse},
+        {RIL_REQUEST_VOICE_RADIO_TECH, radio_1_6::getVoiceRadioTechnologyResponse},
+        {RIL_REQUEST_GET_CELL_INFO_LIST, radio_1_6::getCellInfoListResponse},
+        {RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE, radio_1_6::setCellInfoListRateResponse},
+        {RIL_REQUEST_SET_INITIAL_ATTACH_APN, radio_1_6::setInitialAttachApnResponse},
+        {RIL_REQUEST_IMS_REGISTRATION_STATE, radio_1_6::getImsRegistrationStateResponse},
+        {RIL_REQUEST_IMS_SEND_SMS, radio_1_6::sendImsSmsResponse},
+        {RIL_REQUEST_SIM_TRANSMIT_APDU_BASIC, radio_1_6::iccTransmitApduBasicChannelResponse},
+        {RIL_REQUEST_SIM_OPEN_CHANNEL, radio_1_6::iccOpenLogicalChannelResponse},
+        {RIL_REQUEST_SIM_CLOSE_CHANNEL, radio_1_6::iccCloseLogicalChannelResponse},
+        {RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL, radio_1_6::iccTransmitApduLogicalChannelResponse},
+        {RIL_REQUEST_NV_READ_ITEM, radio_1_6::nvReadItemResponse},
+        {RIL_REQUEST_NV_WRITE_ITEM, radio_1_6::nvWriteItemResponse},
+        {RIL_REQUEST_NV_WRITE_CDMA_PRL, radio_1_6::nvWriteCdmaPrlResponse},
+        {RIL_REQUEST_NV_RESET_CONFIG, radio_1_6::nvResetConfigResponse},
+        {RIL_REQUEST_SET_UICC_SUBSCRIPTION, radio_1_6::setUiccSubscriptionResponse},
+        {RIL_REQUEST_ALLOW_DATA, radio_1_6::setDataAllowedResponse},
+        {RIL_REQUEST_GET_HARDWARE_CONFIG, radio_1_6::getHardwareConfigResponse},
+        {RIL_REQUEST_SIM_AUTHENTICATION, radio_1_6::requestIccSimAuthenticationResponse},
+        {RIL_REQUEST_GET_DC_RT_INFO, NULL}, {RIL_REQUEST_SET_DC_RT_INFO_RATE, NULL},
+        {RIL_REQUEST_SET_DATA_PROFILE, radio_1_6::setDataProfileResponse},
+        {RIL_REQUEST_SHUTDOWN, radio_1_6::requestShutdownResponse},
+        {RIL_REQUEST_GET_RADIO_CAPABILITY, radio_1_6::getRadioCapabilityResponse},
+        {RIL_REQUEST_SET_RADIO_CAPABILITY, radio_1_6::setRadioCapabilityResponse},
+        {RIL_REQUEST_START_LCE, radio_1_6::startLceServiceResponse},
+        {RIL_REQUEST_STOP_LCE, radio_1_6::stopLceServiceResponse},
+        {RIL_REQUEST_PULL_LCEDATA, radio_1_6::pullLceDataResponse},
+        {RIL_REQUEST_GET_ACTIVITY_INFO, radio_1_6::getModemActivityInfoResponse},
+        {RIL_REQUEST_SET_CARRIER_RESTRICTIONS, radio_1_6::setAllowedCarriersResponse},
+        {RIL_REQUEST_GET_CARRIER_RESTRICTIONS, radio_1_6::getAllowedCarriersResponse},
+        {RIL_REQUEST_SEND_DEVICE_STATE, radio_1_6::sendDeviceStateResponse},
+        {RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER, radio_1_6::setIndicationFilterResponse},
+        {RIL_REQUEST_SET_SIM_CARD_POWER, radio_1_6::setSimCardPowerResponse},
+        {RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION,
+         radio_1_6::setCarrierInfoForImsiEncryptionResponse},
+        {RIL_REQUEST_START_NETWORK_SCAN, radio_1_6::startNetworkScanResponse},
+        {RIL_REQUEST_STOP_NETWORK_SCAN, radio_1_6::stopNetworkScanResponse},
+        {RIL_REQUEST_START_KEEPALIVE, radio_1_6::startKeepaliveResponse},
+        {RIL_REQUEST_STOP_KEEPALIVE, radio_1_6::stopKeepaliveResponse},
+        {RIL_REQUEST_GET_MODEM_STACK_STATUS, radio_1_6::getModemStackStatusResponse},
+        {RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE_BITMAP,
+         radio_1_6::getPreferredNetworkTypeBitmapResponse},
+        {RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE_BITMAP,
+         radio_1_6::setPreferredNetworkTypeBitmapResponse},
+        {RIL_REQUEST_EMERGENCY_DIAL, radio_1_6::emergencyDialResponse},
+        {RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS, radio_1_6::setSystemSelectionChannelsResponse},
+        {RIL_REQUEST_ENABLE_MODEM, radio_1_6::enableModemResponse},
+        {RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA,
+         radio_1_6::setSignalStrengthReportingCriteriaResponse},
+        {RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA,
+         radio_1_6::setLinkCapacityReportingCriteriaResponse},
+        {RIL_REQUEST_ENABLE_UICC_APPLICATIONS, radio_1_6::enableUiccApplicationsResponse},
+        {RIL_REQUEST_ARE_UICC_APPLICATIONS_ENABLED, radio_1_6::areUiccApplicationsEnabledResponse},
+        {RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION, radio_1_6::supplySimDepersonalizationResponse},
+        {RIL_REQUEST_CDMA_SEND_SMS_EXPECT_MORE, radio_1_6::sendCdmaSmsExpectMoreResponse},
+        {RIL_REQUEST_GET_BARRING_INFO, radio_1_6::getBarringInfoResponse},
+        {RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY, radio_1_6::setNrDualConnectivityStateResponse},
+        {RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED,
+         radio_1_6::isNrDualConnectivityEnabledResponse},
+        {RIL_REQUEST_ALLOCATE_PDU_SESSION_ID, radio_1_6::allocatePduSessionIdResponse},
+        {RIL_REQUEST_RELEASE_PDU_SESSION_ID, radio_1_6::releasePduSessionIdResponse},
+        {RIL_REQUEST_START_HANDOVER, radio_1_6::startHandoverResponse},
+        {RIL_REQUEST_CANCEL_HANDOVER, radio_1_6::cancelHandoverResponse},
+        {RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP,
+         radio_1_6::setAllowedNetworkTypesBitmapResponse},
+        {RIL_REQUEST_SET_DATA_THROTTLING, radio_1_6::setDataThrottlingResponse},
+        {RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS, radio_1_6::getSystemSelectionChannelsResponse},
+        {RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP,
+         radio_1_6::getAllowedNetworkTypesBitmapResponse},
+        {RIL_REQUEST_GET_SLICING_CONFIG, radio_1_6::getSlicingConfigResponse},
+        {RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS, radio_1_6::getSimPhonebookRecordsResponse},
+        {RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY, radio_1_6::getSimPhonebookCapacityResponse},
+        {RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORDS, radio_1_6::updateSimPhonebookRecordsResponse},
+        {RIL_REQUEST_GET_CELL_INFO_LIST_1_6, radio_1_6::getCellInfoListResponse
+}
diff --git a/guest/hals/ril/reference-libril/ril_config.cpp b/guest/hals/ril/reference-libril/ril_config.cpp
index 5a72db9..80c8aa0 100644
--- a/guest/hals/ril/reference-libril/ril_config.cpp
+++ b/guest/hals/ril/reference-libril/ril_config.cpp
@@ -17,12 +17,15 @@
 
 #define LOG_TAG "RILC"
 
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
 #include <android/hardware/radio/config/1.1/IRadioConfig.h>
-#include <android/hardware/radio/config/1.2/IRadioConfigResponse.h>
-#include <android/hardware/radio/config/1.3/IRadioConfigResponse.h>
-#include <android/hardware/radio/config/1.3/IRadioConfig.h>
 #include <android/hardware/radio/config/1.2/IRadioConfigIndication.h>
-#include <android/hardware/radio/1.1/types.h>
+#include <android/hardware/radio/config/1.2/IRadioConfigResponse.h>
+#include <android/hardware/radio/config/1.3/IRadioConfig.h>
+#include <android/hardware/radio/config/1.3/IRadioConfigResponse.h>
+#include <libradiocompat/RadioConfig.h>
 
 #include <ril.h>
 #include <guest/hals/ril/reference-libril/ril_service.h>
@@ -32,8 +35,6 @@
 using namespace android::hardware::radio::config;
 using namespace android::hardware::radio::config::V1_0;
 using namespace android::hardware::radio::config::V1_3;
-using ::android::hardware::configureRpcThreadpool;
-using ::android::hardware::joinRpcThreadpool;
 using ::android::hardware::Return;
 using ::android::hardware::hidl_string;
 using ::android::hardware::hidl_vec;
@@ -111,7 +112,7 @@
         const ::android::sp<V1_0::IRadioConfigIndication>& radioConfigIndication) {
     pthread_rwlock_t *radioServiceRwlockPtr = radio_1_6::getRadioServiceRwlock(RIL_SOCKET_1);
     int ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
-    assert(ret == 0);
+    CHECK_EQ(ret, 0);
 
     mRadioConfigResponse = radioConfigResponse;
     mRadioConfigIndication = radioConfigIndication;
@@ -141,7 +142,7 @@
     mCounterRadioConfig++;
 
     ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
-    assert(ret == 0);
+    CHECK_EQ(ret, 0);
 
     return Void();
 }
@@ -166,14 +167,14 @@
     }
     size_t slotNum = slotMap.size();
 
-    if (slotNum > RIL_SOCKET_NUM) {
+    if (slotNum > MAX_LOGICAL_MODEM_NUM) {
         RLOGE("setSimSlotsMapping: invalid parameter");
         sendErrorResponse(pRI, RIL_E_INVALID_ARGUMENTS);
         return Void();
     }
 
     for (size_t socket_id = 0; socket_id < slotNum; socket_id++) {
-        if (slotMap[socket_id] >= RIL_SOCKET_NUM) {
+        if (slotMap[socket_id] >= MAX_LOGICAL_MODEM_NUM) {
             RLOGE("setSimSlotsMapping: invalid parameter[%zu]", socket_id);
             sendErrorResponse(pRI, RIL_E_INVALID_ARGUMENTS);
             return Void();
@@ -258,6 +259,9 @@
 
 void radio_1_6::registerConfigService(RIL_RadioFunctions *callbacks, CommandInfo *commands) {
     using namespace android::hardware;
+    using namespace std::string_literals;
+    namespace compat = android::hardware::radio::compat;
+
     RLOGD("Entry %s", __FUNCTION__);
     const char *serviceNames = "default";
 
@@ -268,7 +272,7 @@
 
     pthread_rwlock_t *radioServiceRwlockPtr = getRadioServiceRwlock(0);
     int ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
-    assert(ret == 0);
+    CHECK_EQ(ret, 0);
     RLOGD("registerConfigService: starting V1_2::IConfigRadio %s", serviceNames);
     radioConfigService = new RadioConfigImpl;
 
@@ -279,10 +283,17 @@
     radioConfigService->mRadioConfigResponseV1_2 = NULL;
     radioConfigService->mRadioConfigResponseV1_3 = NULL;
     radioConfigService->mRadioConfigIndicationV1_2 = NULL;
-    android::status_t status = radioConfigService->registerAsService(serviceNames);
-    RLOGD("registerConfigService registerService: status %d", status);
+
+    // use a compat shim to convert HIDL interface to AIDL and publish it
+    // PLEASE NOTE this is a temporary solution
+    static auto aidlHal = ndk::SharedRefBase::make<compat::RadioConfig>(radioConfigService);
+    const auto instance = compat::RadioConfig::descriptor + "/"s + std::string(serviceNames);
+    const auto status = AServiceManager_addService(aidlHal->asBinder().get(), instance.c_str());
+    RLOGD("registerConfigService addService: status %d", status);
+    CHECK_EQ(status, STATUS_OK);
+
     ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
-    assert(ret == 0);
+    CHECK_EQ(ret, 0);
 }
 
 void checkReturnStatus(Return<void>& ret) {
@@ -298,11 +309,11 @@
         int counter = mCounterRadioConfig;
         pthread_rwlock_t *radioServiceRwlockPtr = radio_1_6::getRadioServiceRwlock(0);
         int ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
-        assert(ret == 0);
+        CHECK_EQ(ret, 0);
 
         // acquire wrlock
         ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
-        assert(ret == 0);
+        CHECK_EQ(ret, 0);
 
         // make sure the counter value has not changed
         if (counter == mCounterRadioConfig) {
@@ -320,11 +331,11 @@
 
         // release wrlock
         ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
-        assert(ret == 0);
+        CHECK_EQ(ret, 0);
 
         // Reacquire rdlock
         ret = pthread_rwlock_rdlock(radioServiceRwlockPtr);
-        assert(ret == 0);
+        CHECK_EQ(ret, 0);
     }
 }
 
diff --git a/guest/hals/ril/reference-libril/ril_service.cpp b/guest/hals/ril/reference-libril/ril_service.cpp
index c7b80a6..5fdc3dc 100644
--- a/guest/hals/ril/reference-libril/ril_service.cpp
+++ b/guest/hals/ril/reference-libril/ril_service.cpp
@@ -16,10 +16,21 @@
 
 #define LOG_TAG "RILC"
 
+#include "RefRadioNetwork.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
 #include <android/hardware/radio/1.6/IRadio.h>
 #include <android/hardware/radio/1.6/IRadioIndication.h>
 #include <android/hardware/radio/1.6/IRadioResponse.h>
 #include <android/hardware/radio/1.6/types.h>
+#include <libradiocompat/CallbackManager.h>
+#include <libradiocompat/RadioData.h>
+#include <libradiocompat/RadioMessaging.h>
+#include <libradiocompat/RadioModem.h>
+#include <libradiocompat/RadioSim.h>
+#include <libradiocompat/RadioVoice.h>
 
 #include <android/hardware/radio/deprecated/1.0/IOemHook.h>
 
@@ -37,8 +48,8 @@
 using namespace android::hardware::radio;
 using namespace android::hardware::radio::V1_0;
 using namespace android::hardware::radio::deprecated::V1_0;
-using ::android::hardware::configureRpcThreadpool;
-using ::android::hardware::joinRpcThreadpool;
+using namespace std::string_literals;
+namespace compat = android::hardware::radio::compat;
 using ::android::hardware::Return;
 using ::android::hardware::hidl_bitfield;
 using ::android::hardware::hidl_string;
@@ -119,26 +130,46 @@
 void convertRilSignalStrengthToHal(void *response, size_t responseLen,
         SignalStrength& signalStrength);
 
-void convertRilDataCallToHal(RIL_Data_Call_Response_v11 *dcResponse,
-        SetupDataCallResult& dcResult);
+void convertRilSignalStrengthToHal_1_2(void* response, size_t responseLen,
+                                       V1_2::SignalStrength& signalStrength);
 
 void convertRilSignalStrengthToHal_1_4(void *response, size_t responseLen,
         V1_4::SignalStrength& signalStrength);
 
-void convertRilDataCallToHal(RIL_Data_Call_Response_v11 *dcResponse,
-        ::android::hardware::radio::V1_4::SetupDataCallResult& dcResult);
+void convertRilSignalStrengthToHal_1_6(void* response, size_t responseLen,
+                                       V1_6::SignalStrength& signalStrength);
 
-void convertRilDataCallToHal(RIL_Data_Call_Response_v12 *dcResponse,
-        ::android::hardware::radio::V1_5::SetupDataCallResult& dcResult);
+void convertRilDataCallToHal(RIL_Data_Call_Response_v11* dcResponse, SetupDataCallResult& dcResult);
 
-void convertRilDataCallToHal(RIL_Data_Call_Response_v12 *dcResponse,
-        ::android::hardware::radio::V1_6::SetupDataCallResult& dcResult);
+void convertRilDataCallToHal(RIL_Data_Call_Response_v11* dcResponse,
+                             V1_4::SetupDataCallResult& dcResult);
+
+void convertRilDataCallToHal(RIL_Data_Call_Response_v11* dcResponse,
+                             V1_5::SetupDataCallResult& dcResult);
+
+void convertRilDataCallToHal(RIL_Data_Call_Response_v11* dcResponse,
+                             V1_6::SetupDataCallResult& dcResult);
 
 void convertRilDataCallListToHal(void *response, size_t responseLen,
         hidl_vec<SetupDataCallResult>& dcResultList);
 
+void convertRilDataCallListToHal_1_4(void* response, size_t responseLen,
+                                     hidl_vec<V1_4::SetupDataCallResult>& dcResultList);
+
+void convertRilDataCallListToHal_1_5(void* response, size_t responseLen,
+                                     hidl_vec<V1_5::SetupDataCallResult>& dcResultList);
+
+void convertRilDataCallListToHal_1_6(void* response, size_t responseLen,
+                                     hidl_vec<V1_6::SetupDataCallResult>& dcResultList);
+
 void convertRilCellInfoListToHal(void *response, size_t responseLen, hidl_vec<CellInfo>& records);
 void convertRilCellInfoListToHal_1_2(void *response, size_t responseLen, hidl_vec<V1_2::CellInfo>& records);
+void convertRilCellInfoListToHal_1_4(void* response, size_t responseLen,
+                                     hidl_vec<V1_4::CellInfo>& records);
+void convertRilCellInfoListToHal_1_5(void* response, size_t responseLen,
+                                     hidl_vec<V1_5::CellInfo>& records);
+void convertRilCellInfoListToHal_1_6(void* response, size_t responseLen,
+                                     hidl_vec<V1_6::CellInfo>& records);
 
 void populateResponseInfo(RadioResponseInfo& responseInfo, int serial, int responseType,
                          RIL_Errno e);
@@ -953,11 +984,11 @@
         int counter = isRadioService ? mCounterRadio[slotId] : mCounterOemHook[slotId];
         pthread_rwlock_t *radioServiceRwlockPtr = radio_1_6::getRadioServiceRwlock(slotId);
         int ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
-        assert(ret == 0);
+        CHECK_EQ(ret, 0);
 
         // acquire wrlock
         ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
-        assert(ret == 0);
+        CHECK_EQ(ret, 0);
 
         // make sure the counter value has not changed
         if (counter == (isRadioService ? mCounterRadio[slotId] : mCounterOemHook[slotId])) {
@@ -986,11 +1017,11 @@
 
         // release wrlock
         ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
-        assert(ret == 0);
+        CHECK_EQ(ret, 0);
 
         // Reacquire rdlock
         ret = pthread_rwlock_rdlock(radioServiceRwlockPtr);
-        assert(ret == 0);
+        CHECK_EQ(ret, 0);
     }
 }
 
@@ -1005,7 +1036,7 @@
 
     pthread_rwlock_t *radioServiceRwlockPtr = radio_1_6::getRadioServiceRwlock(mSlotId);
     int ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
-    assert(ret == 0);
+    CHECK_EQ(ret, 0);
 
     mRadioResponse = radioResponseParam;
     mRadioIndication = radioIndicationParam;
@@ -1055,7 +1086,7 @@
     mCounterRadio[mSlotId]++;
 
     ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
-    assert(ret == 0);
+    CHECK_EQ(ret, 0);
 
     // client is connected. Send initial indications.
     android::onNewCommandConnect((RIL_SOCKET_ID) mSlotId);
@@ -2428,7 +2459,7 @@
 #if VDBG
     RLOGD("getCellInfoList_1_6: serial %d", serial);
 #endif
-    dispatchVoid(serial, mSlotId, RIL_REQUEST_GET_CELL_INFO_LIST);
+    dispatchVoid(serial, mSlotId, RIL_REQUEST_GET_CELL_INFO_LIST_1_6);
     return Void();
 }
 
@@ -3501,12 +3532,14 @@
              e = RIL_E_SUCCESS;
          }
          populateResponseInfo(responseInfo, serial, RESPONSE_SOLICITED, e);
-         Return<void> retStatus
-                   = radioService[mSlotId]->mRadioResponseV1_2->setSignalStrengthReportingCriteriaResponse(responseInfo);
+         Return<void> retStatus =
+                 radioService[mSlotId]
+                         ->mRadioResponseV1_2->setSignalStrengthReportingCriteriaResponse(
+                                 responseInfo);
          radioService[mSlotId]->checkReturnStatus(retStatus);
     } else {
-        RLOGE("setIndicationFilterResponse: radioService[%d]->mRadioResponse == NULL",
-           mSlotId);
+        RLOGE("setSignalStrengthReportingCriteria: radioService[%d]->mRadioResponse == NULL",
+              mSlotId);
     }
     return Void();
 }
@@ -4458,8 +4491,9 @@
     populateResponseInfo(responseInfo, serial, RESPONSE_SOLICITED, RIL_E_SUCCESS);
 
     if (radioService[mSlotId]->mRadioResponseV1_5 != NULL) {
-        Return<void> retStatus
-                = radioService[mSlotId]->mRadioResponseV1_5->setInitialAttachApnResponse(responseInfo);
+        Return<void> retStatus =
+                radioService[mSlotId]->mRadioResponseV1_5->setInitialAttachApnResponse_1_5(
+                        responseInfo);
     } else if (radioService[mSlotId]->mRadioResponseV1_4 != NULL) {
         Return<void> retStatus
                 = radioService[mSlotId]->mRadioResponseV1_4->setInitialAttachApnResponse(responseInfo);
@@ -4487,8 +4521,8 @@
     populateResponseInfo(responseInfo, serial, RESPONSE_SOLICITED, RIL_E_SUCCESS);
 
     if (radioService[mSlotId]->mRadioResponseV1_5 != NULL) {
-        Return<void> retStatus
-                = radioService[mSlotId]->mRadioResponseV1_5->setDataProfileResponse(responseInfo);
+        Return<void> retStatus =
+                radioService[mSlotId]->mRadioResponseV1_5->setDataProfileResponse_1_5(responseInfo);
     } else if (radioService[mSlotId]->mRadioResponseV1_4 != NULL) {
         Return<void> retStatus
                 = radioService[mSlotId]->mRadioResponseV1_4->setDataProfileResponse(responseInfo);
@@ -4504,12 +4538,13 @@
     return Void();
 }
 
-Return<void> RadioImpl_1_6::setIndicationFilter_1_5(int32_t /* serial */,
-        hidl_bitfield<::android::hardware::radio::V1_5::IndicationFilter> /* indicationFilter */) {
-    // TODO implement
+Return<void> RadioImpl_1_6::setIndicationFilter_1_5(
+        int32_t serial,
+        hidl_bitfield<::android::hardware::radio::V1_5::IndicationFilter> indicationFilter) {
 #if VDBG
-    RLOGE("setIndicationFilter_1_5: Method is not implemented");
+    RLOGE("setIndicationFilter_1_5: serial %d");
 #endif
+    dispatchInts(serial, mSlotId, RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER, 1, indicationFilter);
     return Void();
 }
 
@@ -4739,14 +4774,14 @@
 
     pthread_rwlock_t *radioServiceRwlockPtr = radio_1_6::getRadioServiceRwlock(mSlotId);
     int ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
-    assert(ret == 0);
+    CHECK_EQ(ret, 0);
 
     mOemHookResponse = oemHookResponseParam;
     mOemHookIndication = oemHookIndicationParam;
     mCounterOemHook[mSlotId]++;
 
     ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
-    assert(ret == 0);
+    CHECK_EQ(ret, 0);
 
     return Void();
 }
@@ -5123,57 +5158,134 @@
     return 0;
 }
 
-int radio_1_6::getCurrentCallsResponse(int slotId,
-                                  int responseType, int serial, RIL_Errno e,
-                                  void *response, size_t responseLen) {
+int radio_1_6::getCurrentCallsResponse(int slotId, int responseType, int serial, RIL_Errno e,
+                                       void* response, size_t responseLen) {
 #if VDBG
     RLOGD("getCurrentCallsResponse: serial %d", serial);
 #endif
 
-    if (radioService[slotId]->mRadioResponse != NULL) {
+    if (radioService[slotId]->mRadioResponseV1_6 != NULL ||
+        radioService[slotId]->mRadioResponseV1_2 != NULL ||
+        radioService[slotId]->mRadioResponse != NULL) {
+        V1_6::RadioResponseInfo responseInfo16 = {};
         RadioResponseInfo responseInfo = {};
-        populateResponseInfo(responseInfo, serial, responseType, e);
-
-        hidl_vec<Call> calls;
-        if ((response == NULL && responseLen != 0)
-                || (responseLen % sizeof(RIL_Call *)) != 0) {
-            RLOGE("getCurrentCallsResponse: Invalid response");
-            if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
+        if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+            populateResponseInfo_1_6(responseInfo16, serial, responseType, e);
         } else {
-            int num = responseLen / sizeof(RIL_Call *);
-            calls.resize(num);
-
-            for (int i = 0 ; i < num ; i++) {
-                RIL_Call *p_cur = ((RIL_Call **) response)[i];
-                /* each call info */
-                calls[i].state = (CallState) p_cur->state;
-                calls[i].index = p_cur->index;
-                calls[i].toa = p_cur->toa;
-                calls[i].isMpty = p_cur->isMpty;
-                calls[i].isMT = p_cur->isMT;
-                calls[i].als = p_cur->als;
-                calls[i].isVoice = p_cur->isVoice;
-                calls[i].isVoicePrivacy = p_cur->isVoicePrivacy;
-                calls[i].number = convertCharPtrToHidlString(p_cur->number);
-                calls[i].numberPresentation = (CallPresentation) p_cur->numberPresentation;
-                calls[i].name = convertCharPtrToHidlString(p_cur->name);
-                calls[i].namePresentation = (CallPresentation) p_cur->namePresentation;
-                if (p_cur->uusInfo != NULL && p_cur->uusInfo->uusData != NULL) {
-                    RIL_UUS_Info *uusInfo = p_cur->uusInfo;
-                    calls[i].uusInfo.resize(1);
-                    calls[i].uusInfo[0].uusType = (UusType) uusInfo->uusType;
-                    calls[i].uusInfo[0].uusDcs = (UusDcs) uusInfo->uusDcs;
-                    // convert uusInfo->uusData to a null-terminated string
-                    char *nullTermStr = strndup(uusInfo->uusData, uusInfo->uusLength);
-                    calls[i].uusInfo[0].uusData = nullTermStr;
-                    free(nullTermStr);
-                }
-            }
+            populateResponseInfo(responseInfo, serial, responseType, e);
         }
+        if ((response == NULL && responseLen != 0) || (responseLen % sizeof(RIL_Call*)) != 0) {
+            RLOGE("getCurrentCallsResponse: Invalid response");
+            if (e == RIL_E_SUCCESS) {
+                responseInfo16.error = V1_6::RadioError::INVALID_RESPONSE;
+                responseInfo.error = RadioError::INVALID_RESPONSE;
+            }
+            return 0;
+        } else {
+            Return<void> retStatus;
+            if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+                hidl_vec<V1_6::Call> calls;
+                int num = responseLen / sizeof(RIL_Call*);
+                calls.resize(num);
 
-        Return<void> retStatus = radioService[slotId]->mRadioResponse->
-                getCurrentCallsResponse(responseInfo, calls);
-        radioService[slotId]->checkReturnStatus(retStatus);
+                for (int i = 0; i < num; i++) {
+                    RIL_Call* p_cur = ((RIL_Call**)response)[i];
+                    /* each call info */
+                    calls[i].base.base.state = (CallState)p_cur->state;
+                    calls[i].base.base.index = p_cur->index;
+                    calls[i].base.base.toa = p_cur->toa;
+                    calls[i].base.base.isMpty = p_cur->isMpty;
+                    calls[i].base.base.isMT = p_cur->isMT;
+                    calls[i].base.base.als = p_cur->als;
+                    calls[i].base.base.isVoice = p_cur->isVoice;
+                    calls[i].base.base.isVoicePrivacy = p_cur->isVoicePrivacy;
+                    calls[i].base.base.number = convertCharPtrToHidlString(p_cur->number);
+                    calls[i].base.base.numberPresentation =
+                            (CallPresentation)p_cur->numberPresentation;
+                    calls[i].base.base.name = convertCharPtrToHidlString(p_cur->name);
+                    calls[i].base.base.namePresentation = (CallPresentation)p_cur->namePresentation;
+                    if (p_cur->uusInfo != NULL && p_cur->uusInfo->uusData != NULL) {
+                        RIL_UUS_Info* uusInfo = p_cur->uusInfo;
+                        calls[i].base.base.uusInfo.resize(1);
+                        calls[i].base.base.uusInfo[0].uusType = (UusType)uusInfo->uusType;
+                        calls[i].base.base.uusInfo[0].uusDcs = (UusDcs)uusInfo->uusDcs;
+                        // convert uusInfo->uusData to a null-terminated string
+                        char* nullTermStr = strndup(uusInfo->uusData, uusInfo->uusLength);
+                        calls[i].base.base.uusInfo[0].uusData = nullTermStr;
+                        free(nullTermStr);
+                    }
+                }
+                retStatus = radioService[slotId]->mRadioResponseV1_6->getCurrentCallsResponse_1_6(
+                        responseInfo16, calls);
+            } else if (radioService[slotId]->mRadioResponseV1_2 != NULL) {
+                hidl_vec<V1_2::Call> calls;
+                int num = responseLen / sizeof(RIL_Call*);
+                calls.resize(num);
+
+                for (int i = 0; i < num; i++) {
+                    RIL_Call* p_cur = ((RIL_Call**)response)[i];
+                    /* each call info */
+                    calls[i].base.state = (CallState)p_cur->state;
+                    calls[i].base.index = p_cur->index;
+                    calls[i].base.toa = p_cur->toa;
+                    calls[i].base.isMpty = p_cur->isMpty;
+                    calls[i].base.isMT = p_cur->isMT;
+                    calls[i].base.als = p_cur->als;
+                    calls[i].base.isVoice = p_cur->isVoice;
+                    calls[i].base.isVoicePrivacy = p_cur->isVoicePrivacy;
+                    calls[i].base.number = convertCharPtrToHidlString(p_cur->number);
+                    calls[i].base.numberPresentation = (CallPresentation)p_cur->numberPresentation;
+                    calls[i].base.name = convertCharPtrToHidlString(p_cur->name);
+                    calls[i].base.namePresentation = (CallPresentation)p_cur->namePresentation;
+                    if (p_cur->uusInfo != NULL && p_cur->uusInfo->uusData != NULL) {
+                        RIL_UUS_Info* uusInfo = p_cur->uusInfo;
+                        calls[i].base.uusInfo.resize(1);
+                        calls[i].base.uusInfo[0].uusType = (UusType)uusInfo->uusType;
+                        calls[i].base.uusInfo[0].uusDcs = (UusDcs)uusInfo->uusDcs;
+                        // convert uusInfo->uusData to a null-terminated string
+                        char* nullTermStr = strndup(uusInfo->uusData, uusInfo->uusLength);
+                        calls[i].base.uusInfo[0].uusData = nullTermStr;
+                        free(nullTermStr);
+                    }
+                }
+                retStatus = radioService[slotId]->mRadioResponseV1_2->getCurrentCallsResponse_1_2(
+                        responseInfo, calls);
+            } else {
+                hidl_vec<Call> calls;
+                int num = responseLen / sizeof(RIL_Call*);
+                calls.resize(num);
+
+                for (int i = 0; i < num; i++) {
+                    RIL_Call* p_cur = ((RIL_Call**)response)[i];
+                    /* each call info */
+                    calls[i].state = (CallState)p_cur->state;
+                    calls[i].index = p_cur->index;
+                    calls[i].toa = p_cur->toa;
+                    calls[i].isMpty = p_cur->isMpty;
+                    calls[i].isMT = p_cur->isMT;
+                    calls[i].als = p_cur->als;
+                    calls[i].isVoice = p_cur->isVoice;
+                    calls[i].isVoicePrivacy = p_cur->isVoicePrivacy;
+                    calls[i].number = convertCharPtrToHidlString(p_cur->number);
+                    calls[i].numberPresentation = (CallPresentation)p_cur->numberPresentation;
+                    calls[i].name = convertCharPtrToHidlString(p_cur->name);
+                    calls[i].namePresentation = (CallPresentation)p_cur->namePresentation;
+                    if (p_cur->uusInfo != NULL && p_cur->uusInfo->uusData != NULL) {
+                        RIL_UUS_Info* uusInfo = p_cur->uusInfo;
+                        calls[i].uusInfo.resize(1);
+                        calls[i].uusInfo[0].uusType = (UusType)uusInfo->uusType;
+                        calls[i].uusInfo[0].uusDcs = (UusDcs)uusInfo->uusDcs;
+                        // convert uusInfo->uusData to a null-terminated string
+                        char* nullTermStr = strndup(uusInfo->uusData, uusInfo->uusLength);
+                        calls[i].uusInfo[0].uusData = nullTermStr;
+                        free(nullTermStr);
+                    }
+                }
+                retStatus = radioService[slotId]->mRadioResponse->getCurrentCallsResponse(
+                        responseInfo, calls);
+            }
+            radioService[slotId]->checkReturnStatus(retStatus);
+        }
     } else {
         RLOGE("getCurrentCallsResponse: radioService[%d]->mRadioResponse == NULL", slotId);
     }
@@ -5268,19 +5380,19 @@
                                                     RIL_Errno e, void *response,
                                                     size_t responseLen) {
 #if VDBG
-    RLOGD("hangupWaitingOrBackgroundResponse: serial %d", serial);
+    RLOGD("hangupForegroundResumeBackgroundResponse: serial %d", serial);
 #endif
 
     if (radioService[slotId]->mRadioResponse != NULL) {
         RadioResponseInfo responseInfo = {};
         populateResponseInfo(responseInfo, serial, responseType, e);
         Return<void> retStatus =
-                radioService[slotId]->mRadioResponse->hangupWaitingOrBackgroundResponse(
-                responseInfo);
+                radioService[slotId]->mRadioResponse->hangupForegroundResumeBackgroundResponse(
+                        responseInfo);
         radioService[slotId]->checkReturnStatus(retStatus);
     } else {
-        RLOGE("hangupWaitingOrBackgroundResponse: radioService[%d]->mRadioResponse == NULL",
-                slotId);
+        RLOGE("hangupForegroundResumeBackgroundResponse: radioService[%d]->mRadioResponse == NULL",
+              slotId);
     }
 
     return 0;
@@ -5362,7 +5474,7 @@
         LastCallFailCauseInfo info = {};
         info.vendorCause = hidl_string();
         if (response == NULL) {
-            RLOGE("getCurrentCallsResponse Invalid response: NULL");
+            RLOGE("getLastCallFailCauseResponse Invalid response: NULL");
             if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
         } else if (responseLen == sizeof(int)) {
             int *pInt = (int *) response;
@@ -5372,7 +5484,7 @@
             info.causeCode = (LastCallFailCause) pFailCauseInfo->cause_code;
             info.vendorCause = convertCharPtrToHidlString(pFailCauseInfo->vendor_cause);
         } else {
-            RLOGE("getCurrentCallsResponse Invalid response: NULL");
+            RLOGE("getLastCallFailCauseResponse Invalid response: NULL");
             if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
         }
 
@@ -5387,46 +5499,59 @@
     return 0;
 }
 
-int radio_1_6::getSignalStrengthResponse(int slotId,
-                                     int responseType, int serial, RIL_Errno e,
-                                     void *response, size_t responseLen) {
+int radio_1_6::getSignalStrengthResponse(int slotId, int responseType, int serial, RIL_Errno e,
+                                         void* response, size_t responseLen) {
 #if VDBG
     RLOGD("getSignalStrengthResponse: serial %d", serial);
 #endif
 
-    if (radioService[slotId]->mRadioResponseV1_4 != NULL) {
-        RadioResponseInfo responseInfo = {};
-        populateResponseInfo(responseInfo, serial, responseType, e);
-        ::android::hardware::radio::V1_4::SignalStrength signalStrength_1_4 = {};
-        if (response == NULL || responseLen != sizeof(RIL_SignalStrength_v12)) {
-            RLOGE("getSignalStrengthResponse: Invalid response");
-            if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
-        } else {
-            convertRilSignalStrengthToHal_1_4(response, responseLen, signalStrength_1_4);
-        }
-
-        //TODO: future implementation needs to fill tdScdma, wcdma and nr signal strength.
-
-        Return<void> retStatus = radioService[slotId]->mRadioResponseV1_4->
-                getSignalStrengthResponse_1_4(responseInfo, signalStrength_1_4);
-        radioService[slotId]->checkReturnStatus(retStatus);
-    } else if (radioService[slotId]->mRadioResponse != NULL) {
-        RadioResponseInfo responseInfo = {};
-        populateResponseInfo(responseInfo, serial, responseType, e);
-        SignalStrength signalStrength = {};
-        if (response == NULL || responseLen != sizeof(RIL_SignalStrength_v12)) {
-            RLOGE("getSignalStrengthResponse: Invalid response");
-            if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
-        } else {
-            convertRilSignalStrengthToHal(response, responseLen, signalStrength);
-        }
-
-        Return<void> retStatus = radioService[slotId]->mRadioResponse->getSignalStrengthResponse(
-                responseInfo, signalStrength);
-        radioService[slotId]->checkReturnStatus(retStatus);
+    V1_6::RadioResponseInfo responseInfo16 = {};
+    RadioResponseInfo responseInfo = {};
+    if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+        populateResponseInfo_1_6(responseInfo16, serial, responseType, e);
     } else {
-        RLOGE("getSignalStrengthResponse: radioService[%d]->mRadioResponse == NULL",
-                slotId);
+        populateResponseInfo(responseInfo, serial, responseType, e);
+    }
+
+    if (response == NULL || responseLen != sizeof(RIL_SignalStrength_v12)) {
+        RLOGE("getSignalStrengthResponse: Invalid response");
+        if (e == RIL_E_SUCCESS) {
+            responseInfo16.error = V1_6::RadioError::INVALID_RESPONSE;
+            responseInfo.error = RadioError::INVALID_RESPONSE;
+        }
+    } else {
+        if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+            V1_6::SignalStrength signalStrength_1_6 = {};
+            convertRilSignalStrengthToHal_1_6(response, responseLen, signalStrength_1_6);
+            Return<void> retStatus =
+                    radioService[slotId]->mRadioResponseV1_6->getSignalStrengthResponse_1_6(
+                            responseInfo16, signalStrength_1_6);
+            radioService[slotId]->checkReturnStatus(retStatus);
+        } else if (radioService[slotId]->mRadioResponseV1_4 != NULL) {
+            V1_4::SignalStrength signalStrength_1_4 = {};
+            convertRilSignalStrengthToHal_1_4(response, responseLen, signalStrength_1_4);
+            // TODO: future implementation needs to fill tdScdma, wcdma and nr signal strength.
+            Return<void> retStatus =
+                    radioService[slotId]->mRadioResponseV1_4->getSignalStrengthResponse_1_4(
+                            responseInfo, signalStrength_1_4);
+            radioService[slotId]->checkReturnStatus(retStatus);
+        } else if (radioService[slotId]->mRadioResponseV1_2 != NULL) {
+            V1_2::SignalStrength signalStrength_1_2 = {};
+            convertRilSignalStrengthToHal_1_2(response, responseLen, signalStrength_1_2);
+            Return<void> retStatus =
+                    radioService[slotId]->mRadioResponseV1_2->getSignalStrengthResponse_1_2(
+                            responseInfo, signalStrength_1_2);
+            radioService[slotId]->checkReturnStatus(retStatus);
+        } else if (radioService[slotId]->mRadioResponse != NULL) {
+            SignalStrength signalStrength = {};
+            convertRilSignalStrengthToHal(response, responseLen, signalStrength);
+            Return<void> retStatus =
+                    radioService[slotId]->mRadioResponse->getSignalStrengthResponse(responseInfo,
+                                                                                    signalStrength);
+            radioService[slotId]->checkReturnStatus(retStatus);
+        } else {
+            RLOGE("getSignalStrengthResponse: radioService[%d]->mRadioResponse == NULL", slotId);
+        }
     }
 
     return 0;
@@ -6483,9 +6608,8 @@
     return 0;
 }
 
-int radio_1_6::getDataRegistrationStateResponse(int slotId,
-                                           int responseType, int serial, RIL_Errno e,
-                                           void *response, size_t responseLen) {
+int radio_1_6::getDataRegistrationStateResponse(int slotId, int responseType, int serial,
+                                                RIL_Errno e, void* response, size_t responseLen) {
 #if VDBG
     RLOGD("getDataRegistrationStateResponse: serial %d", serial);
 #endif
@@ -6547,11 +6671,11 @@
                 }
 
                 Return<void> retStatus =
-                    radioService[slotId]
-                        ->mRadioResponseV1_6
-                        ->getVoiceRegistrationStateResponse_1_6(
-                            responseInfo_1_6, regResponse);
+                        radioService[slotId]
+                                ->mRadioResponseV1_6->getDataRegistrationStateResponse_1_6(
+                                        responseInfo_1_6, regResponse);
                 radioService[slotId]->checkReturnStatus(retStatus);
+                return 0;
             }
         } else if (s_vendorFunctions->version <= 14 &&
                    radioService[slotId]->mRadioResponseV1_5 != NULL) {
@@ -6587,11 +6711,11 @@
                         numStrings, resp);
 
                 Return<void> retStatus =
-                    radioService[slotId]
-                        ->mRadioResponseV1_5
-                        ->getDataRegistrationStateResponse_1_5(
-                            responseInfo, regResponse);
+                        radioService[slotId]
+                                ->mRadioResponseV1_5->getDataRegistrationStateResponse_1_5(
+                                        responseInfo, regResponse);
                 radioService[slotId]->checkReturnStatus(retStatus);
+                return 0;
             }
         } else if (s_vendorFunctions->version <= 14 &&
                     radioService[slotId]->mRadioResponseV1_2 != NULL) {
@@ -6611,6 +6735,7 @@
                 Return<void> retStatus = radioService[slotId]->mRadioResponseV1_2->
                         getDataRegistrationStateResponse_1_2(responseInfo, dataRegResponse);
                 radioService[slotId]->checkReturnStatus(retStatus);
+                return 0;
             }
       } else if (s_vendorFunctions->version <= 14) {
             int numStrings = responseLen / sizeof(char *);
@@ -6865,7 +6990,7 @@
             result.trafficDescriptors =
                     hidl_vec<::android::hardware::radio::V1_6::TrafficDescriptor>();
         } else {
-            convertRilDataCallToHal((RIL_Data_Call_Response_v12 *) response, result);
+            convertRilDataCallToHal((RIL_Data_Call_Response_v11*)response, result);
         }
 
         Return<void> retStatus = radioService[slotId]->mRadioResponseV1_6->setupDataCallResponse_1_6(
@@ -6888,7 +7013,7 @@
             result.gateways = hidl_vec<hidl_string>();
             result.pcscf = hidl_vec<hidl_string>();
         } else {
-            convertRilDataCallToHal((RIL_Data_Call_Response_v12 *) response, result);
+            convertRilDataCallToHal((RIL_Data_Call_Response_v11*)response, result);
         }
 
         Return<void> retStatus = radioService[slotId]->mRadioResponseV1_5->setupDataCallResponse_1_5(
@@ -7605,29 +7730,56 @@
     return 0;
 }
 
-int radio_1_6::getDataCallListResponse(int slotId,
-                                   int responseType, int serial, RIL_Errno e,
-                                   void *response, size_t responseLen) {
+int radio_1_6::getDataCallListResponse(int slotId, int responseType, int serial, RIL_Errno e,
+                                       void* response, size_t responseLen) {
 #if VDBG
     RLOGD("getDataCallListResponse: serial %d", serial);
 #endif
 
-    if (radioService[slotId]->mRadioResponse != NULL) {
+    if (radioService[slotId]->mRadioResponse != NULL ||
+        radioService[slotId]->mRadioResponseV1_4 != NULL ||
+        radioService[slotId]->mRadioResponseV1_5 != NULL ||
+        radioService[slotId]->mRadioResponseV1_6 != NULL) {
+        V1_6::RadioResponseInfo responseInfo16 = {};
         RadioResponseInfo responseInfo = {};
-        populateResponseInfo(responseInfo, serial, responseType, e);
+        if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+            populateResponseInfo_1_6(responseInfo16, serial, responseType, e);
+        } else {
+            populateResponseInfo(responseInfo, serial, responseType, e);
+        }
 
-        hidl_vec<SetupDataCallResult> ret;
         if ((response == NULL && responseLen != 0)
                 || responseLen % sizeof(RIL_Data_Call_Response_v11) != 0) {
             RLOGE("getDataCallListResponse: invalid response");
-            if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
+            if (e == RIL_E_SUCCESS) {
+                responseInfo16.error = V1_6::RadioError::INVALID_RESPONSE;
+                responseInfo.error = RadioError::INVALID_RESPONSE;
+            }
         } else {
-            convertRilDataCallListToHal(response, responseLen, ret);
+            Return<void> retStatus;
+            if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+                hidl_vec<V1_6::SetupDataCallResult> ret;
+                convertRilDataCallListToHal_1_6(response, responseLen, ret);
+                retStatus = radioService[slotId]->mRadioResponseV1_6->getDataCallListResponse_1_6(
+                        responseInfo16, ret);
+            } else if (radioService[slotId]->mRadioResponseV1_5 != NULL) {
+                hidl_vec<V1_5::SetupDataCallResult> ret;
+                convertRilDataCallListToHal_1_5(response, responseLen, ret);
+                retStatus = radioService[slotId]->mRadioResponseV1_5->getDataCallListResponse_1_5(
+                        responseInfo, ret);
+            } else if (radioService[slotId]->mRadioResponseV1_4 != NULL) {
+                hidl_vec<V1_4::SetupDataCallResult> ret;
+                convertRilDataCallListToHal_1_4(response, responseLen, ret);
+                retStatus = radioService[slotId]->mRadioResponseV1_4->getDataCallListResponse_1_4(
+                        responseInfo, ret);
+            } else {
+                hidl_vec<SetupDataCallResult> ret;
+                convertRilDataCallListToHal(response, responseLen, ret);
+                retStatus = radioService[slotId]->mRadioResponse->getDataCallListResponse(
+                        responseInfo, ret);
+            }
+            radioService[slotId]->checkReturnStatus(retStatus);
         }
-
-        Return<void> retStatus = radioService[slotId]->mRadioResponse->getDataCallListResponse(
-                responseInfo, ret);
-        radioService[slotId]->checkReturnStatus(retStatus);
     } else {
         RLOGE("getDataCallListResponse: radioService[%d]->mRadioResponse == NULL", slotId);
     }
@@ -8794,47 +8946,72 @@
     return 0;
 }
 
-int radio_1_6::getCellInfoListResponse(int slotId,
-                                   int responseType,
-                                   int serial, RIL_Errno e, void *response,
-                                   size_t responseLen) {
+int radio_1_6::getCellInfoListResponse(int slotId, int responseType, int serial, RIL_Errno e,
+                                       void* response, size_t responseLen) {
 #if VDBG
     RLOGD("getCellInfoListResponse: serial %d", serial);
 #endif
-
-    if (radioService[slotId]->mRadioResponse != NULL ||
-        radioService[slotId]->mRadioResponseV1_2 != NULL) {
+    if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+        V1_6::RadioResponseInfo responseInfo = {};
+        populateResponseInfo_1_6(responseInfo, serial, responseType, e);
+        hidl_vec<V1_6::CellInfo> ret;
+        Return<void> retStatus;
+        if (response != NULL && responseLen != 0 && responseLen % sizeof(RIL_CellInfo_v16) == 0) {
+            convertRilCellInfoListToHal_1_6(response, responseLen, ret);
+        } else {
+            RLOGE("getCellInfoListResponse_1_6: Invalid response");
+            if (e == RIL_E_SUCCESS) responseInfo.error = V1_6::RadioError::INVALID_RESPONSE;
+        }
+        retStatus = radioService[slotId]->mRadioResponseV1_6->getCellInfoListResponse_1_6(
+                responseInfo, ret);
+        radioService[slotId]->checkReturnStatus(retStatus);
+    } else if (radioService[slotId]->mRadioResponse != NULL ||
+               radioService[slotId]->mRadioResponseV1_2 != NULL ||
+               radioService[slotId]->mRadioResponseV1_4 != NULL ||
+               radioService[slotId]->mRadioResponseV1_5 != NULL) {
         RadioResponseInfo responseInfo = {};
         populateResponseInfo(responseInfo, serial, responseType, e);
-
+        bool error = response == NULL && responseLen != 0;
         Return<void> retStatus;
-        hidl_vec<CellInfo> ret;
-        if ((response == NULL && responseLen != 0)
-                || responseLen % sizeof(RIL_CellInfo_v12) != 0) {
-            RLOGE("getCellInfoListResponse: Invalid response");
-            if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
-
-            if (radioService[slotId]->mRadioResponseV1_2 != NULL) {
-                hidl_vec<V1_2::CellInfo> ret;
-                retStatus = radioService[slotId]->mRadioResponseV1_2->
-                        getCellInfoListResponse_1_2(responseInfo, ret);
+        if (radioService[slotId]->mRadioResponseV1_5 != NULL) {
+            hidl_vec<V1_5::CellInfo> ret;
+            if (!error && responseLen % sizeof(RIL_CellInfo_v16) != 0) {
+                convertRilCellInfoListToHal_1_5(response, responseLen, ret);
             } else {
-                hidl_vec<CellInfo> ret;
-                retStatus = radioService[slotId]->mRadioResponse->
-                        getCellInfoListResponse(responseInfo, ret);
+                RLOGE("getCellInfoListResponse_1_5: Invalid response");
+                if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
             }
-        } else {
-            if (radioService[slotId]->mRadioResponseV1_2 != NULL) {
-                hidl_vec<V1_2::CellInfo> ret;
+            retStatus = radioService[slotId]->mRadioResponseV1_5->getCellInfoListResponse_1_5(
+                    responseInfo, ret);
+        } else if (radioService[slotId]->mRadioResponseV1_4 != NULL) {
+            hidl_vec<V1_4::CellInfo> ret;
+            if (!error && responseLen % sizeof(RIL_CellInfo_v16) != 0) {
+                convertRilCellInfoListToHal_1_4(response, responseLen, ret);
+            } else {
+                RLOGE("getCellInfoListResponse_1_4: Invalid response");
+                if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
+            }
+            radioService[slotId]->mRadioResponseV1_4->getCellInfoListResponse_1_4(responseInfo,
+                                                                                  ret);
+        } else if (radioService[slotId]->mRadioResponseV1_2 != NULL) {
+            hidl_vec<V1_2::CellInfo> ret;
+            if (!error && responseLen % sizeof(RIL_CellInfo_v12) != 0) {
                 convertRilCellInfoListToHal_1_2(response, responseLen, ret);
-                retStatus = radioService[slotId]->mRadioResponseV1_2->
-                        getCellInfoListResponse_1_2(responseInfo, ret);
             } else {
-                hidl_vec<CellInfo> ret;
-                convertRilCellInfoListToHal(response, responseLen, ret);
-                retStatus = radioService[slotId]->mRadioResponse->
-                        getCellInfoListResponse(responseInfo, ret);
+                RLOGE("getCellInfoListResponse_1_2: Invalid response");
+                if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
             }
+            radioService[slotId]->mRadioResponseV1_2->getCellInfoListResponse_1_2(responseInfo,
+                                                                                  ret);
+        } else {
+            hidl_vec<CellInfo> ret;
+            if (!error && responseLen % sizeof(RIL_CellInfo) != 0) {
+                convertRilCellInfoListToHal(response, responseLen, ret);
+            } else {
+                RLOGE("getCellInfoListResponse: Invalid response");
+                if (e == RIL_E_SUCCESS) responseInfo.error = RadioError::INVALID_RESPONSE;
+            }
+            radioService[slotId]->mRadioResponse->getCellInfoListResponse(responseInfo, ret);
         }
         radioService[slotId]->checkReturnStatus(retStatus);
     } else {
@@ -8866,9 +9043,8 @@
     return 0;
 }
 
-int radio_1_6::setInitialAttachApnResponse(int slotId,
-                                       int responseType, int serial, RIL_Errno e,
-                                       void *response, size_t responseLen) {
+int radio_1_6::setInitialAttachApnResponse(int slotId, int responseType, int serial, RIL_Errno e,
+                                           void* response, size_t responseLen) {
 #if VDBG
     RLOGD("setInitialAttachApnResponse: serial %d", serial);
 #endif
@@ -8876,18 +9052,18 @@
     if (radioService[slotId]->mRadioResponseV1_5 != NULL) {
         RadioResponseInfo responseInfo = {};
         populateResponseInfo(responseInfo, serial, responseType, e);
-        Return<void> retStatus
-                = radioService[slotId]->mRadioResponseV1_5->setInitialAttachApnResponse_1_5(
-                responseInfo);
+        Return<void> retStatus =
+                radioService[slotId]->mRadioResponseV1_5->setInitialAttachApnResponse_1_5(
+                        responseInfo);
+        radioService[slotId]->checkReturnStatus(retStatus);
     } else if (radioService[slotId]->mRadioResponse != NULL) {
         RadioResponseInfo responseInfo = {};
         populateResponseInfo(responseInfo, serial, responseType, e);
-        Return<void> retStatus
-                = radioService[slotId]->mRadioResponse->setInitialAttachApnResponse(responseInfo);
+        Return<void> retStatus =
+                radioService[slotId]->mRadioResponse->setInitialAttachApnResponse(responseInfo);
         radioService[slotId]->checkReturnStatus(retStatus);
     } else {
-        RLOGE("setInitialAttachApnResponse: radioService[%d]->mRadioResponse == NULL",
-                slotId);
+        RLOGE("setInitialAttachApnResponse: radioService[%d]->mRadioResponse == NULL", slotId);
     }
 
     return 0;
@@ -9469,6 +9645,7 @@
     RLOGD("setAllowedCarriersResponse: serial %d", serial);
 #endif
     RadioResponseInfo responseInfo = {};
+    populateResponseInfo(responseInfo, serial, responseType, e);
 
     if (radioService[slotId]->mRadioResponseV1_4 != NULL) {
         Return<void> retStatus = radioService[slotId]->mRadioResponseV1_4
@@ -10322,7 +10499,7 @@
     RLOGD("getSlicingConfigResponse: serial %d", serial);
 #endif
 
-    if (radioService[slotId]->mRadioResponse != NULL) {
+    if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
         V1_6::RadioResponseInfo responseInfo = {};
         populateResponseInfo_1_6(responseInfo, serial, responseType, e);
 
@@ -10342,22 +10519,64 @@
 #if VDBG
     RLOGD("getSimPhonebookRecordsResponse: serial %d", serial);
 #endif
+
+    if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+        V1_6::RadioResponseInfo responseInfo = {};
+        populateResponseInfo_1_6(responseInfo, serial, responseType, e);
+
+        Return<void> retStatus =
+                radioService[slotId]->mRadioResponseV1_6->getSimPhonebookRecordsResponse(
+                        responseInfo);
+        radioService[slotId]->checkReturnStatus(retStatus);
+    } else {
+        RLOGE("getSimPhonebookRecordsResponse: radioService[%d]->mRadioResponse == NULL", slotId);
+    }
+
     return 0;
 }
 
 int radio_1_6::getSimPhonebookCapacityResponse(int slotId, int responseType, int serial,
                              RIL_Errno e, void *response, size_t responseLen) {
 #if VDBG
-    RLOGD("getSimPhonebookRecordsResponse: serial %d", serial);
+    RLOGD("getSimPhonebookCapacityResponse: serial %d", serial);
 #endif
+
+    if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+        V1_6::RadioResponseInfo responseInfo = {};
+        populateResponseInfo_1_6(responseInfo, serial, responseType, e);
+
+        V1_6::PhonebookCapacity phonebookCapacity = {};
+        Return<void> retStatus =
+                radioService[slotId]->mRadioResponseV1_6->getSimPhonebookCapacityResponse(
+                        responseInfo, phonebookCapacity);
+        radioService[slotId]->checkReturnStatus(retStatus);
+    } else {
+        RLOGE("getSimPhonebookCapacityResponse: radioService[%d]->mRadioResponse == NULL", slotId);
+    }
+
     return 0;
 }
 
 int radio_1_6::updateSimPhonebookRecordsResponse(int slotId, int responseType, int serial,
                              RIL_Errno e, void *response, size_t responseLen) {
 #if VDBG
-    RLOGD("getSimPhonebookRecordsResponse: serial %d", serial);
+    RLOGD("updateSimPhonebookRecordsResponse: serial %d", serial);
 #endif
+
+    if (radioService[slotId]->mRadioResponseV1_6 != NULL) {
+        V1_6::RadioResponseInfo responseInfo = {};
+        populateResponseInfo_1_6(responseInfo, serial, responseType, e);
+
+        int32_t updatedRecordIndex = 0;
+        Return<void> retStatus =
+                radioService[slotId]->mRadioResponseV1_6->updateSimPhonebookRecordsResponse(
+                        responseInfo, updatedRecordIndex);
+        radioService[slotId]->checkReturnStatus(retStatus);
+    } else {
+        RLOGE("updateSimPhonebookRecordsResponse: radioService[%d]->mRadioResponse == NULL",
+              slotId);
+    }
+
     return 0;
 }
 
@@ -10638,6 +10857,26 @@
     signalStrength.tdScdma.rscp = rilSignalStrength->TD_SCDMA_SignalStrength.rscp;
 }
 
+void convertRilSignalStrengthToHal_1_2(void* response, size_t responseLen,
+                                       V1_2::SignalStrength& signalStrength_1_2) {
+    SignalStrength signalStrength = {};
+    convertRilSignalStrengthToHal(response, responseLen, signalStrength);
+    signalStrength_1_2.gsm = signalStrength.gw;
+    signalStrength_1_2.cdma = signalStrength.cdma;
+    signalStrength_1_2.evdo = signalStrength.evdo;
+    signalStrength_1_2.lte = signalStrength.lte;
+
+    RIL_SignalStrength_v12* rilSignalStrength = (RIL_SignalStrength_v12*)response;
+    signalStrength_1_2.wcdma.base.signalStrength =
+            rilSignalStrength->WCDMA_SignalStrength.signalStrength;
+    signalStrength_1_2.wcdma.base.bitErrorRate =
+            rilSignalStrength->WCDMA_SignalStrength.bitErrorRate;
+    signalStrength_1_2.wcdma.rscp = INT_MAX;
+    signalStrength_1_2.wcdma.ecno = INT_MAX;
+
+    signalStrength_1_2.tdScdma.rscp = INT_MAX;
+}
+
 void convertRilSignalStrengthToHal_1_4(void *response, size_t responseLen,
         V1_4::SignalStrength& signalStrength_1_4) {
     SignalStrength signalStrength = {};
@@ -10667,12 +10906,41 @@
     signalStrength_1_4.nr.csiSinr = rilSignalStrength->NR_SignalStrength.ssSinr;
 }
 
-int radio_1_6::currentSignalStrengthInd(int slotId,
-                                    int indicationType, int token, RIL_Errno e,
-                                    void *response, size_t responseLen) {
-    if (radioService[slotId] != NULL &&
-       (radioService[slotId]->mRadioIndication != NULL ||
-        radioService[slotId]->mRadioIndicationV1_4 != NULL)) {
+void convertRilSignalStrengthToHal_1_6(void* response, size_t responseLen,
+                                       V1_6::SignalStrength& signalStrength_1_6) {
+    SignalStrength signalStrength = {};
+    convertRilSignalStrengthToHal(response, responseLen, signalStrength);
+    signalStrength_1_6.gsm = signalStrength.gw;
+    signalStrength_1_6.cdma = signalStrength.cdma;
+    signalStrength_1_6.evdo = signalStrength.evdo;
+    signalStrength_1_6.lte.base = signalStrength.lte;
+
+    RIL_SignalStrength_v12* rilSignalStrength = (RIL_SignalStrength_v12*)response;
+    signalStrength_1_6.wcdma.base.signalStrength =
+            rilSignalStrength->WCDMA_SignalStrength.signalStrength;
+    signalStrength_1_6.wcdma.base.bitErrorRate =
+            rilSignalStrength->WCDMA_SignalStrength.bitErrorRate;
+    signalStrength_1_6.wcdma.rscp = INT_MAX;
+    signalStrength_1_6.wcdma.ecno = INT_MAX;
+
+    signalStrength_1_6.tdscdma.signalStrength = INT_MAX;
+    signalStrength_1_6.tdscdma.bitErrorRate = INT_MAX;
+    signalStrength_1_6.tdscdma.rscp = INT_MAX;
+
+    signalStrength_1_6.nr.base.ssRsrp = rilSignalStrength->NR_SignalStrength.ssRsrp;
+    signalStrength_1_6.nr.base.ssRsrq = rilSignalStrength->NR_SignalStrength.ssRsrq;
+    signalStrength_1_6.nr.base.ssSinr = rilSignalStrength->NR_SignalStrength.ssSinr;
+    signalStrength_1_6.nr.base.csiRsrp = rilSignalStrength->NR_SignalStrength.csiRsrp;
+    signalStrength_1_6.nr.base.csiRsrq = rilSignalStrength->NR_SignalStrength.csiRsrq;
+    signalStrength_1_6.nr.base.csiSinr = rilSignalStrength->NR_SignalStrength.ssSinr;
+}
+
+int radio_1_6::currentSignalStrengthInd(int slotId, int indicationType, int token, RIL_Errno e,
+                                        void* response, size_t responseLen) {
+    if (radioService[slotId] != NULL && (radioService[slotId]->mRadioIndication != NULL ||
+                                         radioService[slotId]->mRadioIndicationV1_2 != NULL ||
+                                         radioService[slotId]->mRadioIndicationV1_4 != NULL ||
+                                         radioService[slotId]->mRadioIndicationV1_6 != NULL)) {
         if (response == NULL || responseLen != sizeof(RIL_SignalStrength_v12)) {
             RLOGE("currentSignalStrengthInd: invalid response");
             return 0;
@@ -10682,16 +10950,26 @@
         RLOGD("currentSignalStrengthInd");
 #endif
         Return<void> retStatus;
-        if (radioService[slotId]->mRadioIndicationV1_4 != NULL) {
-          V1_4::SignalStrength signalStrength_1_4 = {};
-          convertRilSignalStrengthToHal_1_4(response, responseLen, signalStrength_1_4);
-          retStatus = radioService[slotId]->mRadioIndicationV1_4->currentSignalStrength_1_4(
-                          convertIntToRadioIndicationType(indicationType), signalStrength_1_4);
+        if (radioService[slotId]->mRadioIndicationV1_6 != NULL) {
+            V1_6::SignalStrength signalStrength_1_6 = {};
+            convertRilSignalStrengthToHal_1_6(response, responseLen, signalStrength_1_6);
+            retStatus = radioService[slotId]->mRadioIndicationV1_6->currentSignalStrength_1_6(
+                    convertIntToRadioIndicationType(indicationType), signalStrength_1_6);
+        } else if (radioService[slotId]->mRadioIndicationV1_4 != NULL) {
+            V1_4::SignalStrength signalStrength_1_4 = {};
+            convertRilSignalStrengthToHal_1_4(response, responseLen, signalStrength_1_4);
+            retStatus = radioService[slotId]->mRadioIndicationV1_4->currentSignalStrength_1_4(
+                    convertIntToRadioIndicationType(indicationType), signalStrength_1_4);
+        } else if (radioService[slotId]->mRadioIndicationV1_2 != NULL) {
+            V1_2::SignalStrength signalStrength_1_2 = {};
+            convertRilSignalStrengthToHal_1_2(response, responseLen, signalStrength_1_2);
+            retStatus = radioService[slotId]->mRadioIndicationV1_2->currentSignalStrength_1_2(
+                    convertIntToRadioIndicationType(indicationType), signalStrength_1_2);
         } else {
-          SignalStrength signalStrength = {};
-          convertRilSignalStrengthToHal(response, responseLen, signalStrength);
-          retStatus = radioService[slotId]->mRadioIndication->currentSignalStrength(
-                          convertIntToRadioIndicationType(indicationType), signalStrength);
+            SignalStrength signalStrength = {};
+            convertRilSignalStrengthToHal(response, responseLen, signalStrength);
+            retStatus = radioService[slotId]->mRadioIndication->currentSignalStrength(
+                    convertIntToRadioIndicationType(indicationType), signalStrength);
         }
         radioService[slotId]->checkReturnStatus(retStatus);
     } else {
@@ -10763,8 +11041,8 @@
     dcResult.mtu = dcResponse->mtu;
 }
 
-void convertRilDataCallToHal(RIL_Data_Call_Response_v12 *dcResponse,
-        ::android::hardware::radio::V1_5::SetupDataCallResult& dcResult) {
+void convertRilDataCallToHal(RIL_Data_Call_Response_v11* dcResponse,
+                             ::android::hardware::radio::V1_5::SetupDataCallResult& dcResult) {
     dcResult.cause = (::android::hardware::radio::V1_4::DataCallFailCause) dcResponse->status;
     dcResult.suggestedRetryTime = dcResponse->suggestedRetryTime;
     dcResult.cid = dcResponse->cid;
@@ -10788,12 +11066,12 @@
     dcResult.dnses = split(convertCharPtrToHidlString(dcResponse->dnses));
     dcResult.gateways = split(convertCharPtrToHidlString(dcResponse->gateways));
     dcResult.pcscf = split(convertCharPtrToHidlString(dcResponse->pcscf));
-    dcResult.mtuV4 = dcResponse->mtuV4;
-    dcResult.mtuV6 = dcResponse->mtuV6;
+    dcResult.mtuV4 = dcResponse->mtu;
+    dcResult.mtuV6 = dcResponse->mtu;
 }
 
-void convertRilDataCallToHal(RIL_Data_Call_Response_v12 *dcResponse,
-        ::android::hardware::radio::V1_6::SetupDataCallResult& dcResult) {
+void convertRilDataCallToHal(RIL_Data_Call_Response_v11* dcResponse,
+                             ::android::hardware::radio::V1_6::SetupDataCallResult& dcResult) {
     dcResult.cause = (::android::hardware::radio::V1_6::DataCallFailCause) dcResponse->status;
     dcResult.suggestedRetryTime = dcResponse->suggestedRetryTime;
     dcResult.cid = dcResponse->cid;
@@ -10817,14 +11095,23 @@
     dcResult.dnses = split(convertCharPtrToHidlString(dcResponse->dnses));
     dcResult.gateways = split(convertCharPtrToHidlString(dcResponse->gateways));
     dcResult.pcscf = split(convertCharPtrToHidlString(dcResponse->pcscf));
-    dcResult.mtuV4 = dcResponse->mtuV4;
-    dcResult.mtuV6 = dcResponse->mtuV6;
+    dcResult.mtuV4 = dcResponse->mtu;
+    dcResult.mtuV6 = dcResponse->mtu;
 
     std::vector<::android::hardware::radio::V1_6::TrafficDescriptor> trafficDescriptors;
     ::android::hardware::radio::V1_6::TrafficDescriptor trafficDescriptor;
     ::android::hardware::radio::V1_6::OsAppId osAppId;
 
-    osAppId.osAppId = 1;
+    std::vector<uint8_t> osAppIdVec;
+    osAppIdVec.push_back('o');
+    osAppIdVec.push_back('s');
+    osAppIdVec.push_back('A');
+    osAppIdVec.push_back('p');
+    osAppIdVec.push_back('p');
+    osAppIdVec.push_back('I');
+    osAppIdVec.push_back('d');
+
+    osAppId.osAppId = osAppIdVec;
     trafficDescriptor.osAppId.value(osAppId);
     trafficDescriptors.push_back(trafficDescriptor);
     dcResult.trafficDescriptors = trafficDescriptors;
@@ -10841,22 +11128,76 @@
     }
 }
 
+void convertRilDataCallListToHal_1_4(void* response, size_t responseLen,
+                                     hidl_vec<V1_4::SetupDataCallResult>& dcResultList) {
+    int num = responseLen / sizeof(RIL_Data_Call_Response_v11);
+
+    RIL_Data_Call_Response_v11* dcResponse = (RIL_Data_Call_Response_v11*)response;
+    dcResultList.resize(num);
+    for (int i = 0; i < num; i++) {
+        convertRilDataCallToHal(&dcResponse[i], dcResultList[i]);
+    }
+}
+
+void convertRilDataCallListToHal_1_5(void* response, size_t responseLen,
+                                     hidl_vec<V1_5::SetupDataCallResult>& dcResultList) {
+    int num = responseLen / sizeof(RIL_Data_Call_Response_v11);
+
+    RIL_Data_Call_Response_v11* dcResponse = (RIL_Data_Call_Response_v11*)response;
+    dcResultList.resize(num);
+    for (int i = 0; i < num; i++) {
+        convertRilDataCallToHal(&dcResponse[i], dcResultList[i]);
+    }
+}
+
+void convertRilDataCallListToHal_1_6(void* response, size_t responseLen,
+                                     hidl_vec<V1_6::SetupDataCallResult>& dcResultList) {
+    int num = responseLen / sizeof(RIL_Data_Call_Response_v11);
+
+    RIL_Data_Call_Response_v11* dcResponse = (RIL_Data_Call_Response_v11*)response;
+    dcResultList.resize(num);
+    for (int i = 0; i < num; i++) {
+        convertRilDataCallToHal(&dcResponse[i], dcResultList[i]);
+    }
+}
+
 int radio_1_6::dataCallListChangedInd(int slotId,
                                   int indicationType, int token, RIL_Errno e, void *response,
                                   size_t responseLen) {
-    if (radioService[slotId] != NULL && radioService[slotId]->mRadioIndication != NULL) {
+    if (radioService[slotId] != NULL && (radioService[slotId]->mRadioIndication != NULL ||
+                                         radioService[slotId]->mRadioIndicationV1_4 != NULL ||
+                                         radioService[slotId]->mRadioIndicationV1_5 != NULL ||
+                                         radioService[slotId]->mRadioIndicationV1_6 != NULL)) {
         if ((response == NULL && responseLen != 0)
                 || responseLen % sizeof(RIL_Data_Call_Response_v11) != 0) {
             RLOGE("dataCallListChangedInd: invalid response");
             return 0;
         }
-        hidl_vec<SetupDataCallResult> dcList;
-        convertRilDataCallListToHal(response, responseLen, dcList);
 #if VDBG
         RLOGD("dataCallListChangedInd");
 #endif
-        Return<void> retStatus = radioService[slotId]->mRadioIndication->dataCallListChanged(
-                convertIntToRadioIndicationType(indicationType), dcList);
+        Return<void> retStatus;
+        if (radioService[slotId]->mRadioIndicationV1_6 != NULL) {
+            hidl_vec<V1_6::SetupDataCallResult> dcList;
+            convertRilDataCallListToHal_1_6(response, responseLen, dcList);
+            retStatus = radioService[slotId]->mRadioIndicationV1_6->dataCallListChanged_1_6(
+                    convertIntToRadioIndicationType(indicationType), dcList);
+        } else if (radioService[slotId]->mRadioIndicationV1_5 != NULL) {
+            hidl_vec<V1_5::SetupDataCallResult> dcList;
+            convertRilDataCallListToHal_1_5(response, responseLen, dcList);
+            retStatus = radioService[slotId]->mRadioIndicationV1_5->dataCallListChanged_1_5(
+                    convertIntToRadioIndicationType(indicationType), dcList);
+        } else if (radioService[slotId]->mRadioIndicationV1_4 != NULL) {
+            hidl_vec<V1_4::SetupDataCallResult> dcList;
+            convertRilDataCallListToHal_1_4(response, responseLen, dcList);
+            retStatus = radioService[slotId]->mRadioIndicationV1_4->dataCallListChanged_1_4(
+                    convertIntToRadioIndicationType(indicationType), dcList);
+        } else {
+            hidl_vec<SetupDataCallResult> dcList;
+            convertRilDataCallListToHal(response, responseLen, dcList);
+            retStatus = radioService[slotId]->mRadioIndication->dataCallListChanged(
+                    convertIntToRadioIndicationType(indicationType), dcList);
+        }
         radioService[slotId]->checkReturnStatus(retStatus);
     } else {
         RLOGE("dataCallListChangedInd: radioService[%d]->mRadioIndication == NULL", slotId);
@@ -12045,6 +12386,351 @@
     }
 }
 
+void convertRilCellInfoListToHal_1_5(void* response, size_t responseLen,
+                                     hidl_vec<V1_5::CellInfo>& records) {
+    int num = responseLen / sizeof(RIL_CellInfo_v16);
+    records.resize(num);
+    RIL_CellInfo_v16* rillCellInfo = (RIL_CellInfo_v16*)response;
+    for (int i = 0; i < num; i++) {
+        records[i].registered = rillCellInfo->registered;
+        records[i].connectionStatus = (V1_2::CellConnectionStatus)rillCellInfo->connectionStatus;
+
+        switch (rillCellInfo->cellInfoType) {
+            case RIL_CELL_INFO_TYPE_GSM: {
+                V1_5::CellInfoGsm cellInfoGsm;
+                cellInfoGsm.cellIdentityGsm.base.base.mcc =
+                        std::to_string(rillCellInfo->CellInfo.gsm.cellIdentityGsm.mcc);
+                cellInfoGsm.cellIdentityGsm.base.base.mnc =
+                        ril::util::mnc::decode(rillCellInfo->CellInfo.gsm.cellIdentityGsm.mnc);
+                cellInfoGsm.cellIdentityGsm.base.base.lac =
+                        rillCellInfo->CellInfo.gsm.cellIdentityGsm.lac;
+                cellInfoGsm.cellIdentityGsm.base.base.cid =
+                        rillCellInfo->CellInfo.gsm.cellIdentityGsm.cid;
+                cellInfoGsm.cellIdentityGsm.base.base.arfcn =
+                        rillCellInfo->CellInfo.gsm.cellIdentityGsm.arfcn;
+                cellInfoGsm.cellIdentityGsm.base.base.bsic =
+                        rillCellInfo->CellInfo.gsm.cellIdentityGsm.bsic;
+                cellInfoGsm.signalStrengthGsm.signalStrength =
+                        rillCellInfo->CellInfo.gsm.signalStrengthGsm.signalStrength;
+                cellInfoGsm.signalStrengthGsm.bitErrorRate =
+                        rillCellInfo->CellInfo.gsm.signalStrengthGsm.bitErrorRate;
+                cellInfoGsm.signalStrengthGsm.timingAdvance =
+                        rillCellInfo->CellInfo.gsm.signalStrengthGsm.timingAdvance;
+                records[i].ratSpecificInfo.gsm(cellInfoGsm);
+                break;
+            }
+
+            case RIL_CELL_INFO_TYPE_WCDMA: {
+                V1_5::CellInfoWcdma cellInfoWcdma;
+                cellInfoWcdma.cellIdentityWcdma.base.base.mcc =
+                        std::to_string(rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.mcc);
+                cellInfoWcdma.cellIdentityWcdma.base.base.mnc =
+                        ril::util::mnc::decode(rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.mnc);
+                cellInfoWcdma.cellIdentityWcdma.base.base.lac =
+                        rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.lac;
+                cellInfoWcdma.cellIdentityWcdma.base.base.cid =
+                        rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.cid;
+                cellInfoWcdma.cellIdentityWcdma.base.base.psc =
+                        rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.psc;
+                cellInfoWcdma.cellIdentityWcdma.base.base.uarfcn =
+                        rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.uarfcn;
+                cellInfoWcdma.signalStrengthWcdma.base.signalStrength =
+                        rillCellInfo->CellInfo.wcdma.signalStrengthWcdma.signalStrength;
+                cellInfoWcdma.signalStrengthWcdma.base.bitErrorRate =
+                        rillCellInfo->CellInfo.wcdma.signalStrengthWcdma.bitErrorRate;
+                records[i].ratSpecificInfo.wcdma(cellInfoWcdma);
+                break;
+            }
+
+            case RIL_CELL_INFO_TYPE_CDMA: {
+                V1_2::CellInfoCdma cellInfoCdma;
+                cellInfoCdma.cellIdentityCdma.base.networkId =
+                        rillCellInfo->CellInfo.cdma.cellIdentityCdma.networkId;
+                cellInfoCdma.cellIdentityCdma.base.systemId =
+                        rillCellInfo->CellInfo.cdma.cellIdentityCdma.systemId;
+                cellInfoCdma.cellIdentityCdma.base.baseStationId =
+                        rillCellInfo->CellInfo.cdma.cellIdentityCdma.basestationId;
+                cellInfoCdma.cellIdentityCdma.base.longitude =
+                        rillCellInfo->CellInfo.cdma.cellIdentityCdma.longitude;
+                cellInfoCdma.cellIdentityCdma.base.latitude =
+                        rillCellInfo->CellInfo.cdma.cellIdentityCdma.latitude;
+                cellInfoCdma.signalStrengthCdma.dbm =
+                        rillCellInfo->CellInfo.cdma.signalStrengthCdma.dbm;
+                cellInfoCdma.signalStrengthCdma.ecio =
+                        rillCellInfo->CellInfo.cdma.signalStrengthCdma.ecio;
+                cellInfoCdma.signalStrengthEvdo.dbm =
+                        rillCellInfo->CellInfo.cdma.signalStrengthEvdo.dbm;
+                cellInfoCdma.signalStrengthEvdo.ecio =
+                        rillCellInfo->CellInfo.cdma.signalStrengthEvdo.ecio;
+                cellInfoCdma.signalStrengthEvdo.signalNoiseRatio =
+                        rillCellInfo->CellInfo.cdma.signalStrengthEvdo.signalNoiseRatio;
+                records[i].ratSpecificInfo.cdma(cellInfoCdma);
+                break;
+            }
+
+            case RIL_CELL_INFO_TYPE_LTE: {
+                V1_5::CellInfoLte cellInfoLte;
+                cellInfoLte.cellIdentityLte.base.base.mcc =
+                        std::to_string(rillCellInfo->CellInfo.lte.cellIdentityLte.mcc);
+                cellInfoLte.cellIdentityLte.base.base.mnc =
+                        ril::util::mnc::decode(rillCellInfo->CellInfo.lte.cellIdentityLte.mnc);
+                cellInfoLte.cellIdentityLte.base.base.ci =
+                        rillCellInfo->CellInfo.lte.cellIdentityLte.ci;
+                cellInfoLte.cellIdentityLte.base.base.pci =
+                        rillCellInfo->CellInfo.lte.cellIdentityLte.pci;
+                cellInfoLte.cellIdentityLte.base.base.tac =
+                        rillCellInfo->CellInfo.lte.cellIdentityLte.tac;
+                cellInfoLte.cellIdentityLte.base.base.earfcn =
+                        rillCellInfo->CellInfo.lte.cellIdentityLte.earfcn;
+                cellInfoLte.signalStrengthLte.signalStrength =
+                        rillCellInfo->CellInfo.lte.signalStrengthLte.signalStrength;
+                cellInfoLte.signalStrengthLte.rsrp =
+                        rillCellInfo->CellInfo.lte.signalStrengthLte.rsrp;
+                cellInfoLte.signalStrengthLte.rsrq =
+                        rillCellInfo->CellInfo.lte.signalStrengthLte.rsrq;
+                cellInfoLte.signalStrengthLte.rssnr =
+                        rillCellInfo->CellInfo.lte.signalStrengthLte.rssnr;
+                cellInfoLte.signalStrengthLte.cqi =
+                        rillCellInfo->CellInfo.lte.signalStrengthLte.cqi;
+                cellInfoLte.signalStrengthLte.timingAdvance =
+                        rillCellInfo->CellInfo.lte.signalStrengthLte.timingAdvance;
+                records[i].ratSpecificInfo.lte(cellInfoLte);
+                break;
+            }
+
+            case RIL_CELL_INFO_TYPE_TD_SCDMA: {
+                V1_5::CellInfoTdscdma cellInfoTdscdma;
+                cellInfoTdscdma.cellIdentityTdscdma.base.base.mcc =
+                        std::to_string(rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.mcc);
+                cellInfoTdscdma.cellIdentityTdscdma.base.base.mnc = ril::util::mnc::decode(
+                        rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.mnc);
+                cellInfoTdscdma.cellIdentityTdscdma.base.base.lac =
+                        rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.lac;
+                cellInfoTdscdma.cellIdentityTdscdma.base.base.cid =
+                        rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.cid;
+                cellInfoTdscdma.cellIdentityTdscdma.base.base.cpid =
+                        rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.cpid;
+                cellInfoTdscdma.signalStrengthTdscdma.rscp =
+                        rillCellInfo->CellInfo.tdscdma.signalStrengthTdscdma.rscp;
+                records[i].ratSpecificInfo.tdscdma(cellInfoTdscdma);
+                break;
+            }
+
+            case RIL_CELL_INFO_TYPE_NR: {
+                V1_5::CellInfoNr cellInfoNr;
+                cellInfoNr.cellIdentityNr.base.mcc =
+                        std::to_string(rillCellInfo->CellInfo.nr.cellidentity.mcc);
+                cellInfoNr.cellIdentityNr.base.mnc =
+                        ril::util::mnc::decode(rillCellInfo->CellInfo.nr.cellidentity.mnc);
+                cellInfoNr.cellIdentityNr.base.nci = rillCellInfo->CellInfo.nr.cellidentity.nci;
+                cellInfoNr.cellIdentityNr.base.pci = rillCellInfo->CellInfo.nr.cellidentity.pci;
+                cellInfoNr.cellIdentityNr.base.tac = rillCellInfo->CellInfo.nr.cellidentity.tac;
+                cellInfoNr.cellIdentityNr.base.nrarfcn =
+                        rillCellInfo->CellInfo.nr.cellidentity.nrarfcn;
+                cellInfoNr.cellIdentityNr.base.operatorNames.alphaLong = convertCharPtrToHidlString(
+                        rillCellInfo->CellInfo.nr.cellidentity.operatorNames.alphaLong);
+                cellInfoNr.cellIdentityNr.base.operatorNames.alphaShort =
+                        convertCharPtrToHidlString(
+                                rillCellInfo->CellInfo.nr.cellidentity.operatorNames.alphaShort);
+
+                cellInfoNr.signalStrengthNr.ssRsrp =
+                        rillCellInfo->CellInfo.nr.signalStrength.ssRsrp;
+                cellInfoNr.signalStrengthNr.ssRsrq =
+                        rillCellInfo->CellInfo.nr.signalStrength.ssRsrq;
+                cellInfoNr.signalStrengthNr.ssSinr =
+                        rillCellInfo->CellInfo.nr.signalStrength.ssSinr;
+                cellInfoNr.signalStrengthNr.csiRsrp =
+                        rillCellInfo->CellInfo.nr.signalStrength.csiRsrp;
+                cellInfoNr.signalStrengthNr.csiRsrq =
+                        rillCellInfo->CellInfo.nr.signalStrength.csiRsrq;
+                cellInfoNr.signalStrengthNr.csiSinr =
+                        rillCellInfo->CellInfo.nr.signalStrength.csiSinr;
+                records[i].ratSpecificInfo.nr(cellInfoNr);
+                break;
+            }
+            default: {
+                break;
+            }
+        }
+        rillCellInfo += 1;
+    }
+}
+
+void convertRilCellInfoListToHal_1_6(void* response, size_t responseLen,
+                                     hidl_vec<V1_6::CellInfo>& records) {
+    int num = responseLen / sizeof(RIL_CellInfo_v16);
+    records.resize(num);
+    RIL_CellInfo_v16* rillCellInfo = (RIL_CellInfo_v16*)response;
+    for (int i = 0; i < num; i++) {
+        records[i].registered = rillCellInfo->registered;
+        records[i].connectionStatus = (V1_2::CellConnectionStatus)rillCellInfo->connectionStatus;
+
+        switch (rillCellInfo->cellInfoType) {
+            case RIL_CELL_INFO_TYPE_GSM: {
+                V1_5::CellInfoGsm cellInfoGsm;
+                cellInfoGsm.cellIdentityGsm.base.base.mcc =
+                        std::to_string(rillCellInfo->CellInfo.gsm.cellIdentityGsm.mcc);
+                cellInfoGsm.cellIdentityGsm.base.base.mnc =
+                        ril::util::mnc::decode(rillCellInfo->CellInfo.gsm.cellIdentityGsm.mnc);
+                cellInfoGsm.cellIdentityGsm.base.base.lac =
+                        rillCellInfo->CellInfo.gsm.cellIdentityGsm.lac;
+                cellInfoGsm.cellIdentityGsm.base.base.cid =
+                        rillCellInfo->CellInfo.gsm.cellIdentityGsm.cid;
+                cellInfoGsm.cellIdentityGsm.base.base.arfcn =
+                        rillCellInfo->CellInfo.gsm.cellIdentityGsm.arfcn;
+                cellInfoGsm.cellIdentityGsm.base.base.bsic =
+                        rillCellInfo->CellInfo.gsm.cellIdentityGsm.bsic;
+                cellInfoGsm.signalStrengthGsm.signalStrength =
+                        rillCellInfo->CellInfo.gsm.signalStrengthGsm.signalStrength;
+                cellInfoGsm.signalStrengthGsm.bitErrorRate =
+                        rillCellInfo->CellInfo.gsm.signalStrengthGsm.bitErrorRate;
+                cellInfoGsm.signalStrengthGsm.timingAdvance =
+                        rillCellInfo->CellInfo.gsm.signalStrengthGsm.timingAdvance;
+                records[i].ratSpecificInfo.gsm(cellInfoGsm);
+                break;
+            }
+
+            case RIL_CELL_INFO_TYPE_WCDMA: {
+                V1_5::CellInfoWcdma cellInfoWcdma;
+                cellInfoWcdma.cellIdentityWcdma.base.base.mcc =
+                        std::to_string(rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.mcc);
+                cellInfoWcdma.cellIdentityWcdma.base.base.mnc =
+                        ril::util::mnc::decode(rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.mnc);
+                cellInfoWcdma.cellIdentityWcdma.base.base.lac =
+                        rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.lac;
+                cellInfoWcdma.cellIdentityWcdma.base.base.cid =
+                        rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.cid;
+                cellInfoWcdma.cellIdentityWcdma.base.base.psc =
+                        rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.psc;
+                cellInfoWcdma.cellIdentityWcdma.base.base.uarfcn =
+                        rillCellInfo->CellInfo.wcdma.cellIdentityWcdma.uarfcn;
+                cellInfoWcdma.signalStrengthWcdma.base.signalStrength =
+                        rillCellInfo->CellInfo.wcdma.signalStrengthWcdma.signalStrength;
+                cellInfoWcdma.signalStrengthWcdma.base.bitErrorRate =
+                        rillCellInfo->CellInfo.wcdma.signalStrengthWcdma.bitErrorRate;
+                records[i].ratSpecificInfo.wcdma(cellInfoWcdma);
+                break;
+            }
+
+            case RIL_CELL_INFO_TYPE_CDMA: {
+                V1_2::CellInfoCdma cellInfoCdma;
+                cellInfoCdma.cellIdentityCdma.base.networkId =
+                        rillCellInfo->CellInfo.cdma.cellIdentityCdma.networkId;
+                cellInfoCdma.cellIdentityCdma.base.systemId =
+                        rillCellInfo->CellInfo.cdma.cellIdentityCdma.systemId;
+                cellInfoCdma.cellIdentityCdma.base.baseStationId =
+                        rillCellInfo->CellInfo.cdma.cellIdentityCdma.basestationId;
+                cellInfoCdma.cellIdentityCdma.base.longitude =
+                        rillCellInfo->CellInfo.cdma.cellIdentityCdma.longitude;
+                cellInfoCdma.cellIdentityCdma.base.latitude =
+                        rillCellInfo->CellInfo.cdma.cellIdentityCdma.latitude;
+                cellInfoCdma.signalStrengthCdma.dbm =
+                        rillCellInfo->CellInfo.cdma.signalStrengthCdma.dbm;
+                cellInfoCdma.signalStrengthCdma.ecio =
+                        rillCellInfo->CellInfo.cdma.signalStrengthCdma.ecio;
+                cellInfoCdma.signalStrengthEvdo.dbm =
+                        rillCellInfo->CellInfo.cdma.signalStrengthEvdo.dbm;
+                cellInfoCdma.signalStrengthEvdo.ecio =
+                        rillCellInfo->CellInfo.cdma.signalStrengthEvdo.ecio;
+                cellInfoCdma.signalStrengthEvdo.signalNoiseRatio =
+                        rillCellInfo->CellInfo.cdma.signalStrengthEvdo.signalNoiseRatio;
+                records[i].ratSpecificInfo.cdma(cellInfoCdma);
+                break;
+            }
+
+            case RIL_CELL_INFO_TYPE_LTE: {
+                V1_6::CellInfoLte cellInfoLte;
+                cellInfoLte.cellIdentityLte.base.base.mcc =
+                        std::to_string(rillCellInfo->CellInfo.lte.cellIdentityLte.mcc);
+                cellInfoLte.cellIdentityLte.base.base.mnc =
+                        ril::util::mnc::decode(rillCellInfo->CellInfo.lte.cellIdentityLte.mnc);
+                cellInfoLte.cellIdentityLte.base.base.ci =
+                        rillCellInfo->CellInfo.lte.cellIdentityLte.ci;
+                cellInfoLte.cellIdentityLte.base.base.pci =
+                        rillCellInfo->CellInfo.lte.cellIdentityLte.pci;
+                cellInfoLte.cellIdentityLte.base.base.tac =
+                        rillCellInfo->CellInfo.lte.cellIdentityLte.tac;
+                cellInfoLte.cellIdentityLte.base.base.earfcn =
+                        rillCellInfo->CellInfo.lte.cellIdentityLte.earfcn;
+                cellInfoLte.cellIdentityLte.base.bandwidth = INT_MAX;
+                hidl_vec<V1_5::EutranBands> bands;
+                bands.resize(1);
+                bands[0] = V1_5::EutranBands::BAND_1;
+                cellInfoLte.cellIdentityLte.bands = bands;
+                cellInfoLte.signalStrengthLte.base.signalStrength =
+                        rillCellInfo->CellInfo.lte.signalStrengthLte.signalStrength;
+                cellInfoLte.signalStrengthLte.base.rsrp =
+                        rillCellInfo->CellInfo.lte.signalStrengthLte.rsrp;
+                cellInfoLte.signalStrengthLte.base.rsrq =
+                        rillCellInfo->CellInfo.lte.signalStrengthLte.rsrq;
+                cellInfoLte.signalStrengthLte.base.rssnr =
+                        rillCellInfo->CellInfo.lte.signalStrengthLte.rssnr;
+                cellInfoLte.signalStrengthLte.base.cqi =
+                        rillCellInfo->CellInfo.lte.signalStrengthLte.cqi;
+                cellInfoLte.signalStrengthLte.base.timingAdvance =
+                        rillCellInfo->CellInfo.lte.signalStrengthLte.timingAdvance;
+                records[i].ratSpecificInfo.lte(cellInfoLte);
+                break;
+            }
+
+            case RIL_CELL_INFO_TYPE_TD_SCDMA: {
+                V1_5::CellInfoTdscdma cellInfoTdscdma;
+                cellInfoTdscdma.cellIdentityTdscdma.base.base.mcc =
+                        std::to_string(rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.mcc);
+                cellInfoTdscdma.cellIdentityTdscdma.base.base.mnc = ril::util::mnc::decode(
+                        rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.mnc);
+                cellInfoTdscdma.cellIdentityTdscdma.base.base.lac =
+                        rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.lac;
+                cellInfoTdscdma.cellIdentityTdscdma.base.base.cid =
+                        rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.cid;
+                cellInfoTdscdma.cellIdentityTdscdma.base.base.cpid =
+                        rillCellInfo->CellInfo.tdscdma.cellIdentityTdscdma.cpid;
+                cellInfoTdscdma.signalStrengthTdscdma.rscp =
+                        rillCellInfo->CellInfo.tdscdma.signalStrengthTdscdma.rscp;
+                records[i].ratSpecificInfo.tdscdma(cellInfoTdscdma);
+                break;
+            }
+
+            case RIL_CELL_INFO_TYPE_NR: {
+                V1_6::CellInfoNr cellInfoNr;
+                cellInfoNr.cellIdentityNr.base.mcc =
+                        std::to_string(rillCellInfo->CellInfo.nr.cellidentity.mcc);
+                cellInfoNr.cellIdentityNr.base.mnc =
+                        ril::util::mnc::decode(rillCellInfo->CellInfo.nr.cellidentity.mnc);
+                cellInfoNr.cellIdentityNr.base.nci = rillCellInfo->CellInfo.nr.cellidentity.nci;
+                cellInfoNr.cellIdentityNr.base.pci = rillCellInfo->CellInfo.nr.cellidentity.pci;
+                cellInfoNr.cellIdentityNr.base.tac = rillCellInfo->CellInfo.nr.cellidentity.tac;
+                cellInfoNr.cellIdentityNr.base.nrarfcn =
+                        rillCellInfo->CellInfo.nr.cellidentity.nrarfcn;
+                cellInfoNr.cellIdentityNr.base.operatorNames.alphaLong = convertCharPtrToHidlString(
+                        rillCellInfo->CellInfo.nr.cellidentity.operatorNames.alphaLong);
+                cellInfoNr.cellIdentityNr.base.operatorNames.alphaShort =
+                        convertCharPtrToHidlString(
+                                rillCellInfo->CellInfo.nr.cellidentity.operatorNames.alphaShort);
+
+                cellInfoNr.signalStrengthNr.base.ssRsrp =
+                        rillCellInfo->CellInfo.nr.signalStrength.ssRsrp;
+                cellInfoNr.signalStrengthNr.base.ssRsrq =
+                        rillCellInfo->CellInfo.nr.signalStrength.ssRsrq;
+                cellInfoNr.signalStrengthNr.base.ssSinr =
+                        rillCellInfo->CellInfo.nr.signalStrength.ssSinr;
+                cellInfoNr.signalStrengthNr.base.csiRsrp =
+                        rillCellInfo->CellInfo.nr.signalStrength.csiRsrp;
+                cellInfoNr.signalStrengthNr.base.csiRsrq =
+                        rillCellInfo->CellInfo.nr.signalStrength.csiRsrq;
+                cellInfoNr.signalStrengthNr.base.csiSinr =
+                        rillCellInfo->CellInfo.nr.signalStrength.csiSinr;
+                records[i].ratSpecificInfo.nr(cellInfoNr);
+                break;
+            }
+            default: {
+                break;
+            }
+        }
+        rillCellInfo += 1;
+    }
+}
+
 int radio_1_6::cellInfoListInd(int slotId,
                            int indicationType, int token, RIL_Errno e, void *response,
                            size_t responseLen) {
@@ -12426,111 +13112,197 @@
     return 0;
 }
 
-int radio_1_6::networkScanResultInd(int slotId,
-                                int indicationType, int token, RIL_Errno e, void *response,
-                                size_t responseLen) {
+int radio_1_6::networkScanResultInd(int slotId, int indicationType, int token, RIL_Errno e,
+                                    void* response, size_t responseLen) {
 #if VDBG
     RLOGD("networkScanResultInd");
 #endif
-    if (radioService[slotId] != NULL && radioService[slotId]->mRadioIndicationV1_4 != NULL) {
+    if (radioService[slotId] != NULL && (radioService[slotId]->mRadioIndicationV1_6 != NULL ||
+                                         radioService[slotId]->mRadioIndicationV1_5 != NULL ||
+                                         radioService[slotId]->mRadioIndicationV1_4 != NULL ||
+                                         radioService[slotId]->mRadioIndicationV1_2 != NULL ||
+                                         radioService[slotId]->mRadioIndicationV1_1 != NULL)) {
         if (response == NULL || responseLen == 0) {
             RLOGE("networkScanResultInd: invalid response");
             return 0;
         }
         RLOGD("networkScanResultInd");
 
-#if VDBG
-        RLOGD("networkScanResultInd");
-#endif
-
         RIL_NetworkScanResult *networkScanResult = (RIL_NetworkScanResult *) response;
-
-        V1_1::NetworkScanResult result;
-        result.status = (V1_1::ScanStatus) networkScanResult->status;
-        result.error = (RadioError) networkScanResult->error;
-        convertRilCellInfoListToHal(
-                networkScanResult->network_infos,
-                networkScanResult->network_infos_length * sizeof(RIL_CellInfo_v12),
-                result.networkInfos);
-
-        Return<void> retStatus = radioService[slotId]->mRadioIndicationV1_4->networkScanResult(
-                convertIntToRadioIndicationType(indicationType), result);
+        Return<void> retStatus;
+        if (radioService[slotId]->mRadioIndicationV1_6 != NULL) {
+            V1_6::NetworkScanResult result;
+            result.status = (V1_1::ScanStatus)networkScanResult->status;
+            result.error = (V1_6::RadioError)networkScanResult->error;
+            convertRilCellInfoListToHal_1_6(
+                    networkScanResult->network_infos,
+                    networkScanResult->network_infos_length * sizeof(RIL_CellInfo_v16),
+                    result.networkInfos);
+            retStatus = radioService[slotId]->mRadioIndicationV1_6->networkScanResult_1_6(
+                    convertIntToRadioIndicationType(indicationType), result);
+        } else if (radioService[slotId]->mRadioIndicationV1_5 != NULL) {
+            V1_5::NetworkScanResult result;
+            result.status = (V1_1::ScanStatus)networkScanResult->status;
+            result.error = (RadioError)networkScanResult->error;
+            convertRilCellInfoListToHal_1_5(
+                    networkScanResult->network_infos,
+                    networkScanResult->network_infos_length * sizeof(RIL_CellInfo_v12),
+                    result.networkInfos);
+            retStatus = radioService[slotId]->mRadioIndicationV1_5->networkScanResult_1_5(
+                    convertIntToRadioIndicationType(indicationType), result);
+        } else if (radioService[slotId]->mRadioIndicationV1_4 != NULL) {
+            V1_4::NetworkScanResult result;
+            result.status = (V1_1::ScanStatus)networkScanResult->status;
+            result.error = (RadioError)networkScanResult->error;
+            convertRilCellInfoListToHal_1_4(
+                    networkScanResult->network_infos,
+                    networkScanResult->network_infos_length * sizeof(RIL_CellInfo_v12),
+                    result.networkInfos);
+            retStatus = radioService[slotId]->mRadioIndicationV1_4->networkScanResult_1_4(
+                    convertIntToRadioIndicationType(indicationType), result);
+        } else if (radioService[slotId]->mRadioIndicationV1_2 != NULL) {
+            V1_2::NetworkScanResult result;
+            result.status = (V1_1::ScanStatus)networkScanResult->status;
+            result.error = (RadioError)networkScanResult->error;
+            convertRilCellInfoListToHal_1_2(
+                    networkScanResult->network_infos,
+                    networkScanResult->network_infos_length * sizeof(RIL_CellInfo_v12),
+                    result.networkInfos);
+            retStatus = radioService[slotId]->mRadioIndicationV1_2->networkScanResult_1_2(
+                    convertIntToRadioIndicationType(indicationType), result);
+        } else {
+            V1_1::NetworkScanResult result;
+            result.status = (V1_1::ScanStatus)networkScanResult->status;
+            result.error = (RadioError)networkScanResult->error;
+            convertRilCellInfoListToHal(
+                    networkScanResult->network_infos,
+                    networkScanResult->network_infos_length * sizeof(RIL_CellInfo),
+                    result.networkInfos);
+            retStatus = radioService[slotId]->mRadioIndicationV1_1->networkScanResult(
+                    convertIntToRadioIndicationType(indicationType), result);
+        }
         radioService[slotId]->checkReturnStatus(retStatus);
     } else {
-        RLOGE("networkScanResultInd: radioService[%d]->mRadioIndicationV1_4 == NULL", slotId);
+        RLOGE("networkScanResultInd: radioService[%d]->mRadioIndication == NULL", slotId);
     }
     return 0;
 }
 
-int radio_1_6::carrierInfoForImsiEncryption(int slotId,
-                                  int indicationType, int token, RIL_Errno e, void *response,
-                                  size_t responseLen) {
-    if (radioService[slotId] != NULL && radioService[slotId]->mRadioIndicationV1_4 != NULL) {
+int radio_1_6::carrierInfoForImsiEncryption(int slotId, int indicationType, int token, RIL_Errno e,
+                                            void* response, size_t responseLen) {
+    if (radioService[slotId] != NULL && (radioService[slotId]->mRadioIndicationV1_2 != NULL)) {
         if (response == NULL || responseLen == 0) {
             RLOGE("carrierInfoForImsiEncryption: invalid response");
             return 0;
         }
         RLOGD("carrierInfoForImsiEncryption");
-        Return<void> retStatus = radioService[slotId]->mRadioIndicationV1_4->
-                carrierInfoForImsiEncryption(convertIntToRadioIndicationType(indicationType));
+        Return<void> retStatus =
+                radioService[slotId]->mRadioIndicationV1_2->carrierInfoForImsiEncryption(
+                        convertIntToRadioIndicationType(indicationType));
         radioService[slotId]->checkReturnStatus(retStatus);
     } else {
-        RLOGE("carrierInfoForImsiEncryption: radioService[%d]->mRadioIndicationV1_4 == NULL",
-                slotId);
+        RLOGE("carrierInfoForImsiEncryption: radioService[%d]->mRadioIndication == NULL", slotId);
     }
 
     return 0;
 }
 
-int radio_1_6::reportPhysicalChannelConfigs(int slotId, int indicationType,
-                                            int token, RIL_Errno e,
-                                            void *response,
-                                            size_t responseLen) {
-    if (radioService[slotId] != NULL &&
-        radioService[slotId]->mRadioIndicationV1_4 != NULL) {
-      int *configs = (int *)response;
-      ::android::hardware::hidl_vec<PhysicalChannelConfigV1_4> physChanConfig;
-      physChanConfig.resize(1);
-      physChanConfig[0].base.status =
-          (::android::hardware::radio::V1_2::CellConnectionStatus)configs[0];
-      physChanConfig[0].base.cellBandwidthDownlink = configs[1];
-      physChanConfig[0].rat =
-          (::android::hardware::radio::V1_4::RadioTechnology)configs[2];
-      physChanConfig[0].rfInfo.range(
-          (::android::hardware::radio::V1_4::FrequencyRange)configs[3]);
-      physChanConfig[0].contextIds.resize(1);
-      physChanConfig[0].contextIds[0] = configs[4];
-      RLOGD("reportPhysicalChannelConfigs: %d %d %d %d %d", configs[0],
-            configs[1], configs[2], configs[3], configs[4]);
-      Return<void> retStatus = radioService[slotId]
-          ->mRadioIndicationV1_4->currentPhysicalChannelConfigs_1_4(
-              RadioIndicationType::UNSOLICITED, physChanConfig);
-      radioService[slotId]->checkReturnStatus(retStatus);
-      {
-          // just send the link estimate along with physical channel
-          // config, as it has at least the downlink bandwidth.
-          // Note: the bandwidth is just some hardcoded
-          // value, as there is not way to get that reliably on
-          // virtual devices, as of now.
-          V1_2::LinkCapacityEstimate lce = {
-            .downlinkCapacityKbps = static_cast<uint32_t>(configs[1]),
-            .uplinkCapacityKbps = static_cast<uint32_t>(configs[1])
-          };
-        RLOGD("reporting link capacity estimate download: %d upload: %d",
-                        lce.downlinkCapacityKbps, lce.uplinkCapacityKbps );
-        Return<void> retStatus = radioService[slotId]->mRadioIndicationV1_4->
-            currentLinkCapacityEstimate(RadioIndicationType::UNSOLICITED, lce);
-        radioService[slotId]->checkReturnStatus(retStatus);
-      }
+int radio_1_6::reportPhysicalChannelConfigs(int slotId, int indicationType, int token, RIL_Errno e,
+                                            void* response, size_t responseLen) {
+    if (radioService[slotId] != NULL && (radioService[slotId]->mRadioIndicationV1_6 != NULL ||
+                                         radioService[slotId]->mRadioIndicationV1_4 != NULL ||
+                                         radioService[slotId]->mRadioIndicationV1_2 != NULL)) {
+        int* configs = (int*)response;
+        if (radioService[slotId]->mRadioIndicationV1_6 != NULL) {
+            hidl_vec<V1_6::PhysicalChannelConfig> physChanConfig;
+            physChanConfig.resize(1);
+            physChanConfig[0].status = (V1_2::CellConnectionStatus)configs[0];
+            physChanConfig[0].cellBandwidthDownlinkKhz = configs[1];
+            physChanConfig[0].rat = (V1_4::RadioTechnology)configs[2];
+            physChanConfig[0].contextIds.resize(1);
+            physChanConfig[0].contextIds[0] = configs[4];
+            RLOGD("reportPhysicalChannelConfigs_1_6: %d %d %d %d %d", configs[0], configs[1],
+                  configs[2], configs[3], configs[4]);
+            Return<void> retStatus =
+                    radioService[slotId]->mRadioIndicationV1_6->currentPhysicalChannelConfigs_1_6(
+                            RadioIndicationType::UNSOLICITED, physChanConfig);
+            radioService[slotId]->checkReturnStatus(retStatus);
+            {
+                // Just send the link estimate along with physical channel config, as it has
+                // at least the downlink bandwidth.
+                // Note: the bandwidth is just some hardcoded value, as there is not way to get
+                // that reliably on virtual devices, as of now.
+                V1_6::LinkCapacityEstimate lce = {
+                        .downlinkCapacityKbps = static_cast<uint32_t>(configs[1]),
+                        .uplinkCapacityKbps = static_cast<uint32_t>(configs[1])};
+                RLOGD("reporting link capacity estimate download: %d upload: %d",
+                      lce.downlinkCapacityKbps, lce.uplinkCapacityKbps);
+                Return<void> retStatus =
+                        radioService[slotId]->mRadioIndicationV1_6->currentLinkCapacityEstimate_1_6(
+                                RadioIndicationType::UNSOLICITED, lce);
+                radioService[slotId]->checkReturnStatus(retStatus);
+            }
+        } else if (radioService[slotId]->mRadioIndicationV1_4 != NULL) {
+            hidl_vec<PhysicalChannelConfigV1_4> physChanConfig;
+            physChanConfig.resize(1);
+            physChanConfig[0].base.status = (V1_2::CellConnectionStatus)configs[0];
+            physChanConfig[0].base.cellBandwidthDownlink = configs[1];
+            physChanConfig[0].rat = (V1_4::RadioTechnology)configs[2];
+            physChanConfig[0].rfInfo.range((V1_4::FrequencyRange)configs[3]);
+            physChanConfig[0].contextIds.resize(1);
+            physChanConfig[0].contextIds[0] = configs[4];
+            RLOGD("reportPhysicalChannelConfigs_1_4: %d %d %d %d %d", configs[0], configs[1],
+                  configs[2], configs[3], configs[4]);
+            Return<void> retStatus =
+                    radioService[slotId]->mRadioIndicationV1_4->currentPhysicalChannelConfigs_1_4(
+                            RadioIndicationType::UNSOLICITED, physChanConfig);
+            radioService[slotId]->checkReturnStatus(retStatus);
+            {
+                // Just send the link estimate along with physical channel config, as it has
+                // at least the downlink bandwidth.
+                // Note: the bandwidth is just some hardcoded value, as there is not way to get
+                // that reliably on virtual devices, as of now.
+                V1_2::LinkCapacityEstimate lce = {
+                        .downlinkCapacityKbps = static_cast<uint32_t>(configs[1]),
+                        .uplinkCapacityKbps = static_cast<uint32_t>(configs[1])};
+                RLOGD("reporting link capacity estimate download: %d upload: %d",
+                      lce.downlinkCapacityKbps, lce.uplinkCapacityKbps);
+                Return<void> retStatus =
+                        radioService[slotId]->mRadioIndicationV1_4->currentLinkCapacityEstimate(
+                                RadioIndicationType::UNSOLICITED, lce);
+                radioService[slotId]->checkReturnStatus(retStatus);
+            }
+        } else {
+            hidl_vec<V1_2::PhysicalChannelConfig> physChanConfig;
+            physChanConfig.resize(1);
+            physChanConfig[0].status = (V1_2::CellConnectionStatus)configs[0];
+            physChanConfig[0].cellBandwidthDownlink = configs[1];
+            RLOGD("reportPhysicalChannelConfigs_1_2: %d %d", configs[0], configs[1]);
+            Return<void> retStatus =
+                    radioService[slotId]->mRadioIndicationV1_2->currentPhysicalChannelConfigs(
+                            RadioIndicationType::UNSOLICITED, physChanConfig);
+            radioService[slotId]->checkReturnStatus(retStatus);
+            {
+                // Just send the link estimate along with physical channel config, as it has
+                // at least the downlink bandwidth.
+                // Note: the bandwidth is just some hardcoded value, as there is not way to get
+                // that reliably on virtual devices, as of now.
+                V1_2::LinkCapacityEstimate lce = {
+                        .downlinkCapacityKbps = static_cast<uint32_t>(configs[1]),
+                        .uplinkCapacityKbps = static_cast<uint32_t>(configs[1])};
+                RLOGD("reporting link capacity estimate download: %d upload: %d",
+                      lce.downlinkCapacityKbps, lce.uplinkCapacityKbps);
+                Return<void> retStatus =
+                        radioService[slotId]->mRadioIndicationV1_2->currentLinkCapacityEstimate(
+                                RadioIndicationType::UNSOLICITED, lce);
+                radioService[slotId]->checkReturnStatus(retStatus);
+            }
+        }
     } else {
-      RLOGE(
-          "reportPhysicalChannelConfigs: radioService[%d]->mRadioIndicationV1_4 "
-          "== NULL",
-          slotId);
-      return -1;
+        RLOGE("reportPhysicalChannelConfigs: radioService[%d]->mRadioIndication == NULL", slotId);
+        return -1;
     }
-
-  return 0;
+    return 0;
 }
 
 int radio_1_6::keepaliveStatusInd(int slotId,
@@ -12592,6 +13364,20 @@
     return 0;
 }
 
+template <typename T>
+static void publishRadioHal(std::shared_ptr<compat::DriverContext> ctx, sp<V1_5::IRadio> hidlHal,
+                            std::shared_ptr<compat::CallbackManager> cm, const std::string& slot) {
+    static std::vector<std::shared_ptr<ndk::ICInterface>> gPublishedHals;
+
+    const auto instance = T::descriptor + "/"s + slot;
+    RLOGD("Publishing %s", instance.c_str());
+
+    auto aidlHal = ndk::SharedRefBase::make<T>(ctx, hidlHal, cm);
+    gPublishedHals.push_back(aidlHal);
+    const auto status = AServiceManager_addService(aidlHal->asBinder().get(), instance.c_str());
+    CHECK_EQ(status, STATUS_OK);
+}
+
 void radio_1_6::registerService(RIL_RadioFunctions *callbacks, CommandInfo *commands) {
     using namespace android::hardware;
     int simCount = 1;
@@ -12615,11 +13401,10 @@
     s_vendorFunctions = callbacks;
     s_commands = commands;
 
-    configureRpcThreadpool(1, true /* callerWillJoin */);
     for (int i = 0; i < simCount; i++) {
         pthread_rwlock_t *radioServiceRwlockPtr = getRadioServiceRwlock(i);
         int ret = pthread_rwlock_wrlock(radioServiceRwlockPtr);
-        assert(ret == 0);
+        CHECK_EQ(ret, 0);
 
         RLOGD("sim i = %d registering ...", i);
 
@@ -12629,8 +13414,19 @@
         radioService[i]->mSimCardPowerState = V1_1::CardPowerState::POWER_UP;
         RLOGD("registerService: starting android::hardware::radio::V1_6::IRadio %s for slot %d",
                 serviceNames[i], i);
-        android::status_t status = radioService[i]->registerAsService(serviceNames[i]);
-        LOG_ALWAYS_FATAL_IF(status != android::OK, "status %d", status);
+
+        // use a compat shim to convert HIDL interface to AIDL and publish it
+        // PLEASE NOTE this is a temporary solution
+        auto radioHidl = radioService[i];
+        const auto slot = serviceNames[i];
+        auto context = std::make_shared<compat::DriverContext>();
+        auto callbackMgr = std::make_shared<compat::CallbackManager>(context, radioHidl);
+        publishRadioHal<compat::RadioData>(context, radioHidl, callbackMgr, slot);
+        publishRadioHal<compat::RadioMessaging>(context, radioHidl, callbackMgr, slot);
+        publishRadioHal<compat::RadioModem>(context, radioHidl, callbackMgr, slot);
+        publishRadioHal<cf::ril::RefRadioNetwork>(context, radioHidl, callbackMgr, slot);
+        publishRadioHal<compat::RadioSim>(context, radioHidl, callbackMgr, slot);
+        publishRadioHal<compat::RadioVoice>(context, radioHidl, callbackMgr, slot);
 
         RLOGD("registerService: OemHook is enabled = %s", kOemHookEnabled ? "true" : "false");
         if (kOemHookEnabled) {
@@ -12640,12 +13436,12 @@
         }
 
         ret = pthread_rwlock_unlock(radioServiceRwlockPtr);
-        assert(ret == 0);
+        CHECK_EQ(ret, 0);
     }
 }
 
 void rilc_thread_pool() {
-    joinRpcThreadpool();
+    ABinderProcess_joinThreadPool();
 }
 
 pthread_rwlock_t * radio_1_6::getRadioServiceRwlock(int slotId) {
diff --git a/guest/hals/ril/reference-ril/atchannel.c b/guest/hals/ril/reference-ril/atchannel.c
index 6ea051f..2e2d8b5 100644
--- a/guest/hals/ril/reference-ril/atchannel.c
+++ b/guest/hals/ril/reference-ril/atchannel.c
@@ -897,22 +897,10 @@
 {
     int i;
     int err = 0;
-    bool inEmulator;
 
-    if (0 != pthread_equal(s_tid_reader, pthread_self())) {
-        /* cannot be called from reader thread */
-        return AT_ERROR_INVALID_THREAD;
-    }
-    inEmulator = isInEmulator();
-    if (inEmulator) {
-        pthread_mutex_lock(&s_writeMutex);
-    }
-    pthread_mutex_lock(&s_commandmutex);
-
-    for (i = 0 ; i < HANDSHAKE_RETRY_COUNT ; i++) {
+    for (i = 0; i < HANDSHAKE_RETRY_COUNT && err != AT_ERROR_INVALID_THREAD; i++) {
         /* some stacks start with verbose off */
-        err = at_send_command_full_nolock ("ATE0Q0V1", NO_RESULT,
-                    NULL, NULL, HANDSHAKE_TIMEOUT_MSEC, NULL);
+        err = at_send_command_full("ATE0Q0V1", NO_RESULT, NULL, NULL, HANDSHAKE_TIMEOUT_MSEC, NULL);
 
         if (err == 0) {
             break;
@@ -926,11 +914,6 @@
         sleepMsec(HANDSHAKE_TIMEOUT_MSEC);
     }
 
-    pthread_mutex_unlock(&s_commandmutex);
-    if (inEmulator) {
-        pthread_mutex_unlock(&s_writeMutex);
-    }
-
     return err;
 }
 
diff --git a/guest/hals/ril/reference-ril/reference-ril.c b/guest/hals/ril/reference-ril/reference-ril.c
index 5d17ab4..f3d01e4 100644
--- a/guest/hals/ril/reference-ril/reference-ril.c
+++ b/guest/hals/ril/reference-ril/reference-ril.c
@@ -54,12 +54,12 @@
 
 #define MAX_AT_RESPONSE 0x1000
 
-#define MAX_PDP         3
+#define MAX_PDP 11  // max LTE bearers
 
 /* pathname returned from RIL_REQUEST_SETUP_DATA_CALL / RIL_REQUEST_SETUP_DEFAULT_PDP */
 // This is used if Wifi is not supported, plain old eth0
 #ifdef CUTTLEFISH_ENABLE
-#define PPP_TTY_PATH_ETH0 "rmnet0"
+#define PPP_TTY_PATH_ETH0 "buried_eth0"
 #else
 #define PPP_TTY_PATH_ETH0 "eth0"
 #endif
@@ -208,7 +208,7 @@
 #define CDMA  (RAF_IS95A | RAF_IS95B | RAF_1xRTT)
 #define EVDO  (RAF_EVDO_0 | RAF_EVDO_A | RAF_EVDO_B | RAF_EHRPD)
 #define WCDMA (RAF_HSUPA | RAF_HSDPA | RAF_HSPA | RAF_HSPAP | RAF_UMTS)
-#define LTE   (RAF_LTE | RAF_LTE_CA)
+#define LTE   (RAF_LTE)
 #define NR    (RAF_NR)
 
 typedef struct {
@@ -413,9 +413,8 @@
 };
 
 struct PDPInfo s_PDP[] = {
-     {1, PDP_IDLE},
-     {2, PDP_IDLE},
-     {3, PDP_IDLE},
+        {1, PDP_IDLE}, {2, PDP_IDLE}, {3, PDP_IDLE}, {4, PDP_IDLE},  {5, PDP_IDLE},  {6, PDP_IDLE},
+        {7, PDP_IDLE}, {8, PDP_IDLE}, {9, PDP_IDLE}, {10, PDP_IDLE}, {11, PDP_IDLE},
 };
 
 static void pollSIMState (void *param);
@@ -704,11 +703,10 @@
 {
     int onOff;
 
-    int err;
     ATResponse *p_response = NULL;
 
     if (sState != RADIO_STATE_OFF) {
-        err = at_send_command("AT+CFUN=0", &p_response);
+        at_send_command("AT+CFUN=0", &p_response);
         setRadioState(RADIO_STATE_UNAVAILABLE);
     }
 
@@ -717,6 +715,18 @@
     return;
 }
 
+static void requestNvResetConfig(void* data, size_t datalen __unused, RIL_Token t) {
+    assert(datalen >= sizeof(int*));
+    int nvConfig = ((int*)data)[0];
+    if (nvConfig == 1 /* ResetNvType::RELOAD */) {
+        setRadioState(RADIO_STATE_OFF);
+        // Wait for FW to process radio off before sending radio on for reboot
+        sleep(5);
+        setRadioState(RADIO_STATE_ON);
+    }
+    RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
 static void requestOrSendDataCallList(int cid, RIL_Token *t);
 
 static void onDataCallListChanged(void *param __unused)
@@ -963,6 +973,19 @@
         }
     }
 
+    // If cid = -1, return the data call list without processing CGCONTRDP (setupDataCall)
+    if (cid == -1) {
+        if (t != NULL)
+            RIL_onRequestComplete(*t, RIL_E_SUCCESS, &responses[0],
+                                  sizeof(RIL_Data_Call_Response_v11));
+        else
+            RIL_onUnsolicitedResponse(RIL_UNSOL_DATA_CALL_LIST_CHANGED, responses,
+                                      n * sizeof(RIL_Data_Call_Response_v11));
+        at_response_free(p_response);
+        p_response = NULL;
+        return;
+    }
+
     at_response_free(p_response);
     p_response = NULL;
 
@@ -1197,7 +1220,6 @@
     RIL_Dial *p_dial;
     char *cmd;
     const char *clir;
-    int ret;
 
     p_dial = (RIL_Dial *)data;
 
@@ -1210,7 +1232,7 @@
 
     asprintf(&cmd, "ATD%s%s;", p_dial->address, clir);
 
-    ret = at_send_command(cmd, NULL);
+    at_send_command(cmd, NULL);
 
     free(cmd);
 
@@ -1254,7 +1276,6 @@
 {
     int *p_line;
 
-    int ret;
     char *cmd;
 
     if (getSIMStatus() == SIM_ABSENT) {
@@ -1267,7 +1288,7 @@
     // "Releases a specific active call X"
     asprintf(&cmd, "AT+CHLD=1%d", p_line[0]);
 
-    ret = at_send_command(cmd, NULL);
+    at_send_command(cmd, NULL);
 
     free(cmd);
 
@@ -1289,6 +1310,7 @@
 
     memset(response, 0, sizeof(response));
 
+    // TODO(b/206814247): Rename AT+CSQ command.
     err = at_send_command_singleline("AT+CSQ", "+CSQ:", &p_response);
 
     if (err < 0 || p_response->success == 0) {
@@ -1449,6 +1471,32 @@
     RIL_onRequestComplete(t, RIL_E_SUCCESS, &i, sizeof(i));
 }
 
+static void requestGetCarrierRestrictions(void* data, size_t datalen, RIL_Token t) {
+    RIL_UNUSED_PARM(datalen);
+    RIL_UNUSED_PARM(data);
+
+    // Fixed values. TODO: query modem
+    RIL_Carrier allowed_carriers = {
+            "123",          // mcc
+            "456",          // mnc
+            RIL_MATCH_ALL,  // match_type
+            "",             // match_data
+    };
+
+    RIL_Carrier excluded_carriers;
+
+    RIL_CarrierRestrictionsWithPriority restrictions = {
+            1,                   // len_allowed_carriers
+            0,                   // len_excluded_carriers
+            &allowed_carriers,   // allowed_carriers
+            &excluded_carriers,  // excluded_carriers
+            1,                   // allowedCarriersPrioritized
+            NO_MULTISIM_POLICY   // multiSimPolicy
+    };
+
+    RIL_onRequestComplete(t, RIL_E_SUCCESS, &restrictions, sizeof(restrictions));
+}
+
 static void requestCdmaPrlVersion(int request __unused, void *data __unused,
                                    size_t datalen __unused, RIL_Token t)
 {
@@ -1672,9 +1720,11 @@
     char *line = str, *p;
     int *resp = NULL;
     int skip;
-    int count = 3;
     int commas;
 
+    s_lac = -1;
+    s_cid = -1;
+
     RLOGD("parseRegistrationState. Parsing: %s",str);
     err = at_tok_start(&line);
     if (err < 0) goto error;
@@ -1712,19 +1762,14 @@
         case 0: /* +CREG: <stat> */
             err = at_tok_nextint(&line, &resp[0]);
             if (err < 0) goto error;
-            resp[1] = -1;
-            resp[2] = -1;
-        break;
+            break;
 
         case 1: /* +CREG: <n>, <stat> */
             err = at_tok_nextint(&line, &skip);
             if (err < 0) goto error;
             err = at_tok_nextint(&line, &resp[0]);
             if (err < 0) goto error;
-            resp[1] = -1;
-            resp[2] = -1;
-            if (err < 0) goto error;
-        break;
+            break;
 
         case 2: /* +CREG: <stat>, <lac>, <cid> */
             err = at_tok_nextint(&line, &resp[0]);
@@ -1758,13 +1803,16 @@
             if (err < 0) goto error;
             err = at_tok_nextint(&line, &resp[3]);
             if (err < 0) goto error;
-            count = 4;
         break;
         default:
             goto error;
     }
-    s_lac = resp[1];
-    s_cid = resp[2];
+
+    if (commas >= 2) {
+        s_lac = resp[1];
+        s_cid = resp[2];
+    }
+
     if (response)
         *response = resp;
     if (items)
@@ -1893,8 +1941,12 @@
     } else { // type == RADIO_TECH_3GPP
         RLOGD("registration state type: 3GPP");
         startfrom = 0;
-        asprintf(&responseStr[1], "%x", registration[1]);
-        asprintf(&responseStr[2], "%x", registration[2]);
+        if (count > 1) {
+            asprintf(&responseStr[1], "%x", registration[1]);
+        }
+        if (count > 2) {
+            asprintf(&responseStr[2], "%x", registration[2]);
+        }
         if (count > 3) {
             asprintf(&responseStr[3], "%d", mapNetworkRegistrationResponse(registration[3]));
         }
@@ -2615,7 +2667,6 @@
     RIL_UNUSED_PARM(datalen);
 
     int err, len;
-    int instruction = 0;
     char *cmd = NULL;
     char *line = NULL;
     RIL_SIM_APDU *p_args = NULL;
@@ -2660,7 +2711,6 @@
     sscanf(&(sr.simResponse[len - 4]), "%02x%02x", &(sr.sw1), &(sr.sw2));
     sr.simResponse[len - 4] = '\0';
 
-    instruction = p_args->instruction;
     RIL_onRequestComplete(t, RIL_E_SUCCESS, &sr, sizeof(sr));
     at_response_free(p_response);
     return;
@@ -2765,9 +2815,9 @@
             goto error;
         }
 
-        qmistatus = system("netcfg rmnet0 dhcp");
+        qmistatus = system("netcfg buried_eth0 dhcp");
 
-        RLOGD("netcfg rmnet0 dhcp: status %d\n", qmistatus);
+        RLOGD("netcfg buried_eth0 dhcp: status %d\n", qmistatus);
 
         if (qmistatus < 0) goto error;
 
@@ -2784,7 +2834,24 @@
         }
 
         cid = getPDP();
-        if (cid < 1 ) goto error;
+        if (cid < 1) {
+            RLOGE("SETUP_DATA_CALL MAX_PDP reached.");
+            RIL_Data_Call_Response_v11 response;
+            response.status = 0x41 /* PDP_FAIL_MAX_ACTIVE_PDP_CONTEXT_REACHED */;
+            response.suggestedRetryTime = -1;
+            response.cid = cid;
+            response.active = -1;
+            response.type = "";
+            response.ifname = "";
+            response.addresses = "";
+            response.dnses = "";
+            response.gateways = "";
+            response.pcscf = "";
+            response.mtu = 0;
+            RIL_onRequestComplete(t, RIL_E_SUCCESS, &response, sizeof(RIL_Data_Call_Response_v11));
+            at_response_free(p_response);
+            return;
+        }
 
         asprintf(&cmd, "AT+CGDCONT=%d,\"%s\",\"%s\",,0,0", cid, pdp_type, apn);
         //FIXME check for error here
@@ -2835,6 +2902,7 @@
     rilErrno = setInterfaceState(radioInterfaceName, kInterfaceDown);
     RIL_onRequestComplete(t, rilErrno, NULL, 0);
     putPDP(cid);
+    requestOrSendDataCallList(-1, NULL);
 }
 
 static void requestSMSAcknowledge(void *data, size_t datalen __unused, RIL_Token t)
@@ -2851,14 +2919,22 @@
 
     if (ackSuccess == 1) {
         err = at_send_command("AT+CNMA=1", NULL);
+        if (err < 0) {
+            goto error;
+        }
     } else if (ackSuccess == 0)  {
         err = at_send_command("AT+CNMA=2", NULL);
+        if (err < 0) {
+            goto error;
+        }
     } else {
         RLOGE("unsupported arg to RIL_REQUEST_SMS_ACKNOWLEDGE\n");
         goto error;
     }
 
     RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+
+    return;
 error:
     RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
 }
@@ -3264,6 +3340,36 @@
     RIL_onRequestComplete(t, RIL_E_SUCCESS, ci, sizeof(ci));
 }
 
+static void requestGetCellInfoList_1_6(void* data __unused, size_t datalen __unused, RIL_Token t) {
+    RIL_CellInfo_v16 ci[1] = {{    // ci[0]
+                               3,  // cellInfoType
+                               1,  // registered
+                               CELL_CONNECTION_PRIMARY_SERVING,
+                               {        // union CellInfo
+                                .lte = {// RIL_CellInfoLte_v12 lte
+                                        {
+                                                // RIL_CellIdentityLte_v12
+                                                // lte.cellIdentityLte
+                                                s_mcc,  // mcc
+                                                s_mnc,  // mnc
+                                                s_cid,  // ci
+                                                0,      // pci
+                                                s_lac,  // tac
+                                                7,      // earfcn
+                                        },
+                                        {
+                                                // RIL_LTE_SignalStrength_v8
+                                                // lte.signalStrengthLte
+                                                10,      // signalStrength
+                                                44,      // rsrp
+                                                3,       // rsrq
+                                                30,      // rssnr
+                                                0,       // cqi
+                                                INT_MAX  // timingAdvance invalid value
+                                        }}}}};
+
+    RIL_onRequestComplete(t, RIL_E_SUCCESS, ci, sizeof(ci));
+}
 
 static void requestSetCellInfoListRate(void *data, size_t datalen __unused, RIL_Token t)
 {
@@ -4670,6 +4776,10 @@
             requestGetCellInfoList(data, datalen, t);
             break;
 
+        case RIL_REQUEST_GET_CELL_INFO_LIST_1_6:
+            requestGetCellInfoList_1_6(data, datalen, t);
+            break;
+
         case RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE:
             requestSetCellInfoListRate(data, datalen, t);
             break;
@@ -4716,6 +4826,7 @@
         case RIL_REQUEST_ALLOW_DATA:
         case RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION:
         case RIL_REQUEST_SET_BAND_MODE:
+        case RIL_REQUEST_SET_CARRIER_RESTRICTIONS:
         case RIL_REQUEST_GET_NEIGHBORING_CELL_IDS:
         case RIL_REQUEST_SET_LOCATION_UPDATES:
         case RIL_REQUEST_SET_TTY_MODE:
@@ -4723,6 +4834,10 @@
             RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
             break;
 
+        case RIL_REQUEST_NV_RESET_CONFIG:
+            requestNvResetConfig(data, datalen, t);
+            break;
+
         case RIL_REQUEST_BASEBAND_VERSION:
             requestCdmaBaseBandVersion(request, data, datalen, t);
             break;
@@ -4922,6 +5037,8 @@
         case RIL_REQUEST_GET_SLICING_CONFIG:
             RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
             break;
+        case RIL_REQUEST_GET_CARRIER_RESTRICTIONS:
+            requestGetCarrierRestrictions(data, datalen, t);
 
         // Radio config requests
         case RIL_REQUEST_CONFIG_GET_SLOT_STATUS:
@@ -5004,6 +5121,9 @@
         case RIL_REQUEST_STOP_KEEPALIVE:
             RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
             break;
+        case RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER:
+            RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+            break;
         default:
             RLOGD("Request not supported. Tech: %d",TECH(sMdmInfo));
             RIL_onRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
@@ -6018,7 +6138,7 @@
             response, sizeof(response));
         free(line);
     } else if (strStartsWith(s, "+CUSATEND")) {  // session end
-      RIL_onUnsolicitedResponse(RIL_UNSOL_STK_SESSION_END, NULL, 0);
+        RIL_onUnsolicitedResponse(RIL_UNSOL_STK_SESSION_END, NULL, 0);
     } else if (strStartsWith(s, "+CUSATP:")) {
         line = p = strdup(s);
         if (!line) {
@@ -6235,6 +6355,10 @@
     pthread_attr_init (&attr);
     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
     ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL);
+    if (ret < 0) {
+        RLOGE("pthread_create: %s:", strerror(errno));
+        return NULL;
+    }
 
     return &s_callbacks;
 }
diff --git a/host/commands/tapsetiff/Android.bp b/guest/hals/wpa_supplicant/Android.bp
similarity index 65%
copy from host/commands/tapsetiff/Android.bp
copy to guest/hals/wpa_supplicant/Android.bp
index 1d7dedb..888b6e0 100644
--- a/host/commands/tapsetiff/Android.bp
+++ b/guest/hals/wpa_supplicant/Android.bp
@@ -1,5 +1,4 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,10 +13,15 @@
 // limitations under the License.
 
 package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
+    default_applicable_licenses: [
+        "external_wpa_supplicant_8_license",
+    ],
 }
 
-sh_binary_host {
-    name: "tapsetiff",
-    src: "tapsetiff.py",
+cc_binary {
+    name: "wpa_supplicant_cf",
+    defaults: ["wpa_supplicant_defaults"],
+    static_libs: [
+        "lib_driver_cmd_simulated_cf_bp",
+    ],
 }
diff --git a/guest/libs/wpa_supplicant_8_lib/Android.bp b/guest/libs/wpa_supplicant_8_lib/Android.bp
new file mode 100644
index 0000000..d09457e
--- /dev/null
+++ b/guest/libs/wpa_supplicant_8_lib/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "lib_driver_cmd_simulated_cf_bp",
+    srcs: ["driver_cmd_nl80211.c"],
+    cflags: ["-DCONFIG_ANDROID_LOG"],
+    header_libs: [
+        "wpa_supplicant_headers",
+    ],
+    shared_libs: [
+        "libc",
+        "libcutils",
+        "libnl",
+    ],
+    soc_specific: true,
+}
diff --git a/guest/monitoring/cuttlefish_service/Android.bp b/guest/monitoring/cuttlefish_service/Android.bp
index 133d233..d33d0e7 100644
--- a/guest/monitoring/cuttlefish_service/Android.bp
+++ b/guest/monitoring/cuttlefish_service/Android.bp
@@ -20,6 +20,9 @@
     name: "CuttlefishService",
     vendor: true,
     srcs: ["java/**/*.java"],
+    resource_dirs: [
+        "res",
+    ],
     static_libs: ["guava"],
     sdk_version: "28",
     privileged: true,
diff --git a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/BluetoothChecker.java b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/BluetoothChecker.java
index 23d4fec..000a96d 100644
--- a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/BluetoothChecker.java
+++ b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/BluetoothChecker.java
@@ -16,6 +16,8 @@
 package com.android.google.gce.gceservice;
 
 import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.res.Resources;
 import android.util.Log;
 
 /*
@@ -28,8 +30,13 @@
     private final GceFuture<Boolean> mEnabled = new GceFuture<Boolean>("Bluetooth");
 
 
-    public BluetoothChecker() {
+    public BluetoothChecker(Context context) {
         super(LOG_TAG);
+        Resources res = context.getResources();
+        if (!res.getBoolean(R.bool.config_bt_required)) {
+            Log.i(LOG_TAG, "Bluetooth not required, assuming enabled.");
+            mEnabled.set(true);
+        }
     }
 
 
diff --git a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/GceService.java b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/GceService.java
index 80d497a..ef0aa03 100644
--- a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/GceService.java
+++ b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/GceService.java
@@ -51,8 +51,8 @@
     private final JobExecutor mExecutor = new JobExecutor();
     private final EventReporter mEventReporter = new EventReporter();
     private final GceBroadcastReceiver mBroadcastReceiver = new GceBroadcastReceiver();
-    private final BluetoothChecker mBluetoothChecker = new BluetoothChecker();
 
+    private BluetoothChecker mBluetoothChecker = null;
     private ConnectivityChecker mConnChecker;
     private GceWifiManager mWifiManager = null;
     private String mMostRecentAction = null;
@@ -76,6 +76,7 @@
             mWindowManager = getSystemService(WindowManager.class);
             mConnChecker = new ConnectivityChecker(this, mEventReporter);
             mWifiManager = new GceWifiManager(this, mEventReporter, mExecutor);
+            mBluetoothChecker = new BluetoothChecker(this);
 
             mPreviousRotation = getRotation();
             mPreviousScreenBounds = getScreenBounds();
diff --git a/guest/monitoring/cuttlefish_service/res/values/config.xml b/guest/monitoring/cuttlefish_service/res/values/config.xml
new file mode 100644
index 0000000..692a686
--- /dev/null
+++ b/guest/monitoring/cuttlefish_service/res/values/config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <!-- Is BT required for boot complete -->
+    <bool name="config_bt_required">true</bool>
+</resources>
diff --git a/guest/monitoring/tombstone_transmit/Android.bp b/guest/monitoring/tombstone_transmit/Android.bp
index d24985a..149274c 100644
--- a/guest/monitoring/tombstone_transmit/Android.bp
+++ b/guest/monitoring/tombstone_transmit/Android.bp
@@ -17,13 +17,9 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-cc_binary {
-    name: "tombstone_transmit",
-    srcs: [
-        "tombstone_transmit.cpp",
-    ],
+cc_defaults {
+    name: "tombstone_transmit_defaults",
     static_libs: [
-        "libcuttlefish_fs_product",
         "libgflags",
         "libbase",
         "libcutils",
@@ -32,7 +28,36 @@
         "liblog",
     ],
     stl: "libc++_static",
-    defaults: ["cuttlefish_guest_product_only"],
+}
+
+cc_binary {
+    name: "tombstone_transmit",
+    srcs: [
+        "tombstone_transmit.cpp",
+    ],
+    static_libs: [
+        "libcuttlefish_fs_product",
+    ],
+    defaults: [
+        "tombstone_transmit_defaults",
+        "cuttlefish_guest_product_only"
+    ],
+}
+
+// Same as tombstone_transmit, but place the binary in /system partition
+// We are using the binary for microdroid, which does not have /product partition
+cc_binary {
+    name: "tombstone_transmit.microdroid",
+    srcs: [
+        "tombstone_transmit.cpp",
+    ],
+    static_libs: [
+        "libcuttlefish_fs",
+    ],
+    defaults: [
+        "tombstone_transmit_defaults",
+        "cuttlefish_base",
+    ],
 }
 
 cc_binary {
diff --git a/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp b/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp
index 3d08780..0853b14 100644
--- a/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp
+++ b/guest/monitoring/tombstone_transmit/tombstone_transmit.cpp
@@ -55,31 +55,40 @@
 }
 
 #define INOTIFY_MAX_EVENT_SIZE (sizeof(struct inotify_event) + NAME_MAX + 1)
-static std::string get_next_tombstone_path_blocking(int fd) {
+static std::vector<std::string> get_next_tombstones_path_blocking(int fd) {
   char event_readout[INOTIFY_MAX_EVENT_SIZE];
-  struct inotify_event *i = (inotify_event*)(event_readout);
-
+  int bytes_parsed = 0;
+  std::vector<std::string> tombstone_paths;
+  // Each successful read can contain one or more of inotify_event events
+  // Note: read() on inotify returns 'whole' events, will never partially
+  // populate the buffer.
   int event_read_out_length = read(fd, event_readout, INOTIFY_MAX_EVENT_SIZE);
 
   if(event_read_out_length == -1) {
     ALOGE("%s: Couldn't read out inotify event due to error: '%s' (%d)",
       __FUNCTION__, strerror(errno), errno);
-    return std::string();
+    return std::vector<std::string>();
   }
 
-  // Create event didn't show up for some reason or no file name was present
-  if(event_read_out_length == sizeof(struct inotify_event)) {
-    ALOGE("%s: inotify event didn't contain filename",__FUNCTION__);
-    return std::string();
+  while (bytes_parsed < event_read_out_length) {
+    struct inotify_event* event =
+        reinterpret_cast<inotify_event*>(event_readout + bytes_parsed);
+    bytes_parsed += sizeof(struct inotify_event) + event->len;
+
+    // No file name was present
+    if (event->len == 0) {
+      ALOGE("%s: inotify event didn't contain filename", __FUNCTION__);
+      continue;
+    }
+    if (!(event->mask & IN_CREATE)) {
+      ALOGE("%s: inotify event didn't pertain to file creation", __FUNCTION__);
+      continue;
+    }
+    tombstone_paths.push_back(std::string(TOMBSTONE_DIR) +
+                              std::string(event->name));
   }
 
-  if(!(i->mask & IN_CREATE)) {
-    ALOGE("%s: inotify event didn't pertain to file creation",__FUNCTION__);
-    return std::string();
-  }
-
-  std::string ret_value(TOMBSTONE_DIR);
-  return ret_value + i->name;
+  return tombstone_paths;
 }
 
 DEFINE_uint32(port,
@@ -103,34 +112,33 @@
   LOG(INFO) << "tombstone watcher successfully initialized";
 
   while (true) {
-    std::string ts_path = get_next_tombstone_path_blocking(
-      file_create_notification_handle);
+    std::vector<std::string> ts_paths =
+        get_next_tombstones_path_blocking(file_create_notification_handle);
+    for (auto& ts_path : ts_paths) {
+      auto log_fd =
+          cuttlefish::SharedFD::VsockClient(FLAGS_cid, FLAGS_port, SOCK_STREAM);
+      std::ifstream ifs(ts_path);
+      char buffer[TOMBSTONE_BUFFER_SIZE];
+      uint num_transfers = 0;
+      int num_bytes_read = 0;
+      while (log_fd->IsOpen() && ifs.is_open() && !ifs.eof()) {
+        ifs.read(buffer, sizeof(buffer));
+        num_bytes_read += ifs.gcount();
+        log_fd->Write(buffer, ifs.gcount());
+        num_transfers++;
+      }
 
-    if(ts_path.empty()) {continue;}
-
-    auto log_fd = cuttlefish::SharedFD::VsockClient(FLAGS_cid, FLAGS_port,
-      SOCK_STREAM);
-
-    std::ifstream ifs(ts_path);
-    char buffer[TOMBSTONE_BUFFER_SIZE];
-    uint num_transfers = 0;
-    int num_bytes_read = 0;
-    while (log_fd->IsOpen() && ifs.is_open() && !ifs.eof()) {
-      ifs.read(buffer, sizeof(buffer));
-      num_bytes_read += ifs.gcount();
-      log_fd->Write(buffer, ifs.gcount());
-      num_transfers++;
-    }
-
-    if (!log_fd->IsOpen()) {
-      ALOGE("Unable to connect to vsock:%u:%u: %s", FLAGS_cid, FLAGS_port,
-        log_fd->StrError());
-    } else if (!ifs.is_open()) {
-      ALOGE("%s closed in the middle of readout.", ts_path.c_str());
-    } else {
-      LOG(INFO) << num_bytes_read << " chars transferred from " <<
-        ts_path.c_str() << " over " << num_transfers << " "
-        << TOMBSTONE_BUFFER_SIZE << " byte sized transfers";
+      if (!log_fd->IsOpen()) {
+        auto error = log_fd->StrError();
+        ALOGE("Unable to connect to vsock:%u:%u: %s", FLAGS_cid, FLAGS_port,
+              error.c_str());
+      } else if (!ifs.is_open()) {
+        ALOGE("%s closed in the middle of readout.", ts_path.c_str());
+      } else {
+        LOG(INFO) << num_bytes_read << " chars transferred from "
+                  << ts_path.c_str() << " over " << num_transfers << " "
+                  << TOMBSTONE_BUFFER_SIZE << " byte sized transfers";
+      }
     }
   }
 
diff --git a/guest/services/wifi/Android.bp b/guest/services/wifi/Android.bp
new file mode 100644
index 0000000..4a5fd53
--- /dev/null
+++ b/guest/services/wifi/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+genrule {
+    name: "init.wifi.sh_apex_srcs",
+    srcs: ["init.wifi.sh"],
+    out: ["init.wifi.sh_apex"],
+    cmd: "sed -E 's/\\/vendor\\/bin\\/mac802/\\/apex\\/com.android.wifi.hal\\/bin\\/mac802/' $(in) > $(out)",
+}
+
+sh_binary {
+    name: "init.wifi.sh_apex",
+    src: ":init.wifi.sh_apex_srcs",
+    filename: "init.wifi.sh",
+    vendor: true,
+    installable: false,
+    init_rc: [
+        "init.wifi.sh.rc",
+    ],
+}
+
+sh_binary {
+    name: "init.wifi.sh",
+    src: "init.wifi.sh",
+    vendor: true,
+    init_rc: [
+        "init.wifi.sh.rc",
+    ],
+}
diff --git a/guest/services/wifi/init.wifi.sh b/guest/services/wifi/init.wifi.sh
new file mode 100755
index 0000000..a23f174
--- /dev/null
+++ b/guest/services/wifi/init.wifi.sh
@@ -0,0 +1,7 @@
+#!/vendor/bin/sh
+
+wifi_mac_prefix=`getprop ro.boot.wifi_mac_prefix`
+if [ -n "$wifi_mac_prefix" ]; then
+    /vendor/bin/mac80211_create_radios 2 $wifi_mac_prefix || exit 1
+fi
+
diff --git a/guest/services/wifi/init.wifi.sh.rc b/guest/services/wifi/init.wifi.sh.rc
new file mode 100644
index 0000000..9a0daee
--- /dev/null
+++ b/guest/services/wifi/init.wifi.sh.rc
@@ -0,0 +1,8 @@
+
+service init_wifi_sh /vendor/bin/init.wifi.sh
+    class late_start
+    user root
+    group root wakelock wifi
+    oneshot
+    disabled    # Started on post-fs-data
+
diff --git a/host/commands/adbshell/main.cpp b/host/commands/adbshell/main.cpp
deleted file mode 100644
index 7263f4c..0000000
--- a/host/commands/adbshell/main.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* Utility that uses an adb connection as the login shell. */
-
-#include <array>
-#include <cassert>
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-#include <string>
-#include <vector>
-
-#include <errno.h>
-#include <unistd.h>
-
-#include "common/libs/utils/environment.h"
-#include "host/libs/config/cuttlefish_config.h"
-
-// Many of our users interact with CVDs via ssh. They expect to be able to
-// get an Android shell (as opposed to the host shell) with a single command.
-//
-// Our goals are to:
-//
-//   * Allow the user to select which CVD to connect to
-//
-//   * Avoid modifications to the host-side sshd and the protocol
-//
-// We accomplish this by using specialized accounts: vsoc-## and cvd-## and
-// specific Android serial numbers:
-//
-//    The vsoc-01 account provides a host-side shell that controls the first CVD
-//    The cvd-01 account is connected to the Andorid shell of the first CVD
-//    The first CVD has a serial number of CUTTLEFISHCVD01
-//
-// The code in the commands/launch directory also follows these conventions by
-// default.
-//
-
-namespace {
-std::string VsocUser() {
-  std::string user = cuttlefish::StringFromEnv("USER", "");
-  assert(!user_cstring.empty());
-
-  std::string cvd_prefix = "cvd-";
-  if (user.find(cvd_prefix) == 0) {
-    user.replace(0, cvd_prefix.size(), cuttlefish::kVsocUserPrefix);
-  }
-  return user;
-}
-
-std::string CuttlefishConfigLocation() {
-  return std::string("/home/") + VsocUser() +
-         "/cuttlefish_runtime/cuttlefish_config.json";
-}
-
-std::string CuttlefishFindAdb() {
-  std::string rval = std::string("/home/") + VsocUser() + "/bin/adb";
-  if (TEMP_FAILURE_RETRY(access(rval.c_str(), X_OK)) == -1) {
-    return "/usr/bin/adb";
-  }
-  return rval;
-}
-
-void SetCuttlefishConfigEnv() {
-  setenv(cuttlefish::kCuttlefishConfigEnvVarName, CuttlefishConfigLocation().c_str(),
-         true);
-}
-}  // namespace
-
-int main(int argc, char* argv[]) {
-  SetCuttlefishConfigEnv();
-  auto instance = cuttlefish::CuttlefishConfig::Get()
-      ->ForDefaultInstance().adb_device_name();
-  std::string adb_path = CuttlefishFindAdb();
-
-  std::vector<char*> new_argv = {
-      const_cast<char*>(adb_path.c_str()), const_cast<char*>("-s"),
-      const_cast<char*>(instance.c_str()), const_cast<char*>("shell"),
-      const_cast<char*>("/system/bin/sh")};
-
-  // Some important data is lost before this point, and there are
-  // no great recovery options:
-  // * ssh with no arguments comes in with 1 arg of -adbshell. The command
-  //   given above does the right thing if we don't invoke the shell.
-  if (argc == 1) {
-    new_argv.back() = nullptr;
-  }
-  // * simple shell commands come in with a -c and a single string. The
-  //   problem here is that adb doesn't preserve spaces, so we need
-  //   to do additional escaping. The best compromise seems to be to
-  //   throw double quotes around each string.
-  for (int i = 1; i < argc; ++i) {
-    size_t buf_size = std::strlen(argv[i]) + 4;
-    new_argv.push_back(new char[buf_size]);
-    std::snprintf(new_argv.back(), buf_size, "\"%s\"", argv[i]);
-  }
-  //
-  // * scp seems to be pathologically broken when paths contain spaces.
-  //   spaces aren't properly escaped by gcloud, so scp will fail with
-  //   "scp: with ambiguous target." We might be able to fix this with
-  //   some creative parsing of the arguments, but that seems like
-  //   overkill.
-  new_argv.push_back(nullptr);
-  execv(new_argv[0], new_argv.data());
-  // This never should happen
-  return 2;
-}
diff --git a/host/commands/append_squashfs_overlay/Android.bp b/host/commands/append_squashfs_overlay/Android.bp
new file mode 100644
index 0000000..882bcb9
--- /dev/null
+++ b/host/commands/append_squashfs_overlay/Android.bp
@@ -0,0 +1,12 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary_host {
+    name: "append_squashfs_overlay",
+    crate_name: "append_squashfs_overlay",
+    srcs: ["src/main.rs"],
+    rustlibs: [
+        "libclap",
+    ],
+}
\ No newline at end of file
diff --git a/host/commands/append_squashfs_overlay/Cargo.toml b/host/commands/append_squashfs_overlay/Cargo.toml
new file mode 100644
index 0000000..5472580
--- /dev/null
+++ b/host/commands/append_squashfs_overlay/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "append_squashfs_overlay"
+version = "0.1.0"
+authors = ["Jeongik Cha <jeongik@google.com>"]
+edition = 2021
+
+[dependencies]
+clap = "2.33"
\ No newline at end of file
diff --git a/host/commands/append_squashfs_overlay/OWNERS b/host/commands/append_squashfs_overlay/OWNERS
new file mode 100644
index 0000000..0930c9e
--- /dev/null
+++ b/host/commands/append_squashfs_overlay/OWNERS
@@ -0,0 +1 @@
+jeongik@google.com
\ No newline at end of file
diff --git a/host/commands/append_squashfs_overlay/src/main.rs b/host/commands/append_squashfs_overlay/src/main.rs
new file mode 100644
index 0000000..281aef8
--- /dev/null
+++ b/host/commands/append_squashfs_overlay/src/main.rs
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! `append_squashfs_overlay` generates a new squashfs image(dest) which contains an overlay image(overlay) on an squashfs image(src).
+//! The tool ignores the existing overlay image in src, that is, the overlay image could be replaced with a new overlay image.
+use std::fs::File;
+use std::io::{copy, Error, ErrorKind, Read, Result, Seek, SeekFrom};
+use std::path::Path;
+
+use clap::{App, Arg};
+
+// https://dr-emann.github.io/squashfs/squashfs.html
+const BYTES_USED_FIELD_POS: u64 = (32 * 5 + 16 * 6 + 64) / 8;
+const SQUASHFS_MAGIC: u32 = 0x73717368;
+
+// https://git.openwrt.org/?p=project/fstools.git;a=blob;f=libfstools/rootdisk.c;h=9f2317f14e8d8f12c71b30944138d7a6c877b406;hb=refs/heads/master#l125
+// 64kb alignment
+const ROOTDEV_OVERLAY_ALIGN: u64 = 64 * 1024;
+
+fn align_size(size: u64, alignment: u64) -> u64 {
+    assert!(
+        alignment > 0 && (alignment & (alignment - 1) == 0),
+        "alignment should be greater than 0 and a power of 2."
+    );
+    (size + (alignment - 1)) & !(alignment - 1)
+}
+
+fn merge_fs(src: &Path, overlay: &Path, dest: &Path, overwrite: bool) -> Result<()> {
+    if dest.exists() && !overwrite {
+        return Err(Error::new(
+            ErrorKind::AlreadyExists,
+            "The destination file already exists, add -w option to overwrite.",
+        ));
+    }
+    let mut buffer = [0; 4];
+
+    let mut src = File::open(src)?;
+
+    src.read_exact(&mut buffer)?;
+    let magic = u32::from_le_bytes(buffer);
+    if magic != SQUASHFS_MAGIC {
+        return Err(Error::new(ErrorKind::InvalidData, "The source image isn't a squashfs image."));
+    }
+    src.seek(SeekFrom::Start(BYTES_USED_FIELD_POS))?;
+    let mut buffer = [0; 8];
+    src.read_exact(&mut buffer)?;
+
+    // https://git.openwrt.org/?p=project/fstools.git;a=blob;f=libfstools/rootdisk.c;h=9f2317f14e8d8f12c71b30944138d7a6c877b406;hb=refs/heads/master#l125
+    // use little endian
+    let bytes_used = u64::from_le_bytes(buffer);
+    let mut dest = File::create(dest)?;
+    let mut overlay = File::open(overlay)?;
+
+    src.seek(SeekFrom::Start(0))?;
+    let mut src_handle = src.take(align_size(bytes_used, ROOTDEV_OVERLAY_ALIGN));
+    copy(&mut src_handle, &mut dest)?;
+    copy(&mut overlay, &mut dest)?;
+    Ok(())
+}
+
+fn main() -> Result<()> {
+    let matches = App::new("append_squashfs_overlay")
+        .arg(Arg::with_name("src").required(true))
+        .arg(Arg::with_name("overlay").required(true))
+        .arg(Arg::with_name("dest").required(true))
+        .arg(
+            Arg::with_name("overwrite")
+                .short("w")
+                .required(false)
+                .takes_value(false)
+                .help("whether the tool overwrite dest or not"),
+        )
+        .get_matches();
+
+    let src = matches.value_of("src").unwrap().as_ref();
+    let overlay = matches.value_of("overlay").unwrap().as_ref();
+    let dest = matches.value_of("dest").unwrap().as_ref();
+    let overwrite = matches.is_present("overwrite");
+
+    merge_fs(src, overlay, dest, overwrite)?;
+    Ok(())
+}
diff --git a/host/commands/assemble_cvd/Android.bp b/host/commands/assemble_cvd/Android.bp
index 4ed4452..a2d4ac3 100644
--- a/host/commands/assemble_cvd/Android.bp
+++ b/host/commands/assemble_cvd/Android.bp
@@ -25,8 +25,10 @@
         "boot_config.cc",
         "boot_image_utils.cc",
         "clean.cc",
+        "disk_builder.cpp",
         "disk_flags.cc",
         "flags.cc",
+        "flag_feature.cpp",
         "misc_info.cc",
         "super_image_mixer.cc",
     ],
@@ -34,9 +36,11 @@
         "bootimg_headers",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libbase",
+        "libfruit",
         "libjsoncpp",
         "libnl",
         "libprotobuf-cpp-full",
@@ -51,10 +55,12 @@
         "libsparse",
         "libcuttlefish_graphics_detector",
         "libcuttlefish_host_config",
+        "libcuttlefish_host_config_adb",
         "libcuttlefish_vm_manager",
         "libgflags",
     ],
     required: [
+        "mkenvimage_slim",
         "lz4",
     ],
     defaults: ["cuttlefish_host", "cuttlefish_libicuuc"],
diff --git a/host/commands/assemble_cvd/assemble_cvd.cc b/host/commands/assemble_cvd/assemble_cvd.cc
index 27eadb5..70607fa 100644
--- a/host/commands/assemble_cvd/assemble_cvd.cc
+++ b/host/commands/assemble_cvd/assemble_cvd.cc
@@ -23,18 +23,24 @@
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
 #include "common/libs/utils/tee_logging.h"
 #include "host/commands/assemble_cvd/clean.h"
 #include "host/commands/assemble_cvd/disk_flags.h"
+#include "host/commands/assemble_cvd/flag_feature.h"
 #include "host/commands/assemble_cvd/flags.h"
+#include "host/libs/config/adb/adb.h"
+#include "host/libs/config/config_flag.h"
+#include "host/libs/config/custom_actions.h"
 #include "host/libs/config/fetcher_config.h"
 
 using cuttlefish::StringFromEnv;
 
 DEFINE_string(assembly_dir, StringFromEnv("HOME", ".") + "/cuttlefish_assembly",
               "A directory to put generated files common between instances");
-DEFINE_string(instance_dir, StringFromEnv("HOME", ".") + "/cuttlefish_runtime",
-              "A directory to put all instance specific files");
+DEFINE_string(instance_dir, StringFromEnv("HOME", ".") + "/cuttlefish",
+              "This is a directory that will hold the cuttlefish generated"
+              "files, including both instance-specific and common files");
 DEFINE_bool(resume, true, "Resume using the disk from the last session, if "
                           "possible. i.e., if --noresume is passed, the disk "
                           "will be reset to the state it was initially launched "
@@ -65,55 +71,78 @@
   return config.ForDefaultInstance().PerInstancePath("cuttlefish_config.json");
 }
 
-bool SaveConfig(const CuttlefishConfig& tmp_config_obj) {
+Result<void> SaveConfig(const CuttlefishConfig& tmp_config_obj) {
   auto config_file = GetConfigFilePath(tmp_config_obj);
   auto config_link = GetGlobalConfigFileLink();
   // Save the config object before starting any host process
-  if (!tmp_config_obj.SaveToFile(config_file)) {
-    LOG(ERROR) << "Unable to save config object";
-    return false;
-  }
+  CF_EXPECT(tmp_config_obj.SaveToFile(config_file),
+            "Failed to save to \"" << config_file << "\"");
   auto legacy_config_file = GetLegacyConfigFilePath(tmp_config_obj);
-  if (!tmp_config_obj.SaveToFile(legacy_config_file)) {
-    LOG(ERROR) << "Unable to save legacy config object";
-    return false;
-  }
+  CF_EXPECT(tmp_config_obj.SaveToFile(legacy_config_file),
+            "Failed to save to \"" << legacy_config_file << "\"");
+
   setenv(kCuttlefishConfigEnvVarName, config_file.c_str(), true);
   if (symlink(config_file.c_str(), config_link.c_str()) != 0) {
-    LOG(ERROR) << "Failed to create symlink to config file at " << config_link
-               << ": " << strerror(errno);
-    return false;
+    return CF_ERRNO("symlink(\"" << config_file << "\", \"" << config_link
+                                 << ") failed");
   }
 
-  return true;
-}
-
-void ValidateAdbModeFlag(const CuttlefishConfig& config) {
-  auto adb_modes = config.adb_mode();
-  adb_modes.erase(AdbMode::Unknown);
-  if (adb_modes.size() < 1) {
-    LOG(INFO) << "ADB not enabled";
-  }
+  return {};
 }
 
 #ifndef O_TMPFILE
 # define O_TMPFILE (020000000 | O_DIRECTORY)
 #endif
 
-const CuttlefishConfig* InitFilesystemAndCreateConfig(
-    FetcherConfig fetcher_config, KernelConfig kernel_config) {
-  std::string assembly_dir_parent = AbsolutePath(FLAGS_assembly_dir);
-  while (assembly_dir_parent[assembly_dir_parent.size() - 1] == '/') {
-    assembly_dir_parent =
-        assembly_dir_parent.substr(0, FLAGS_assembly_dir.rfind('/'));
+Result<void> CreateLegacySymlinks(
+    const CuttlefishConfig::InstanceSpecific& instance) {
+  std::string log_files[] = {
+      "kernel.log",  "launcher.log",        "logcat",
+      "metrics.log", "modem_simulator.log", "crosvm_openwrt.log",
+  };
+  for (const auto& log_file : log_files) {
+    auto symlink_location = instance.PerInstancePath(log_file.c_str());
+    auto log_target = "logs/" + log_file;  // Relative path
+    if (symlink(log_target.c_str(), symlink_location.c_str()) != 0) {
+      return CF_ERRNO("symlink(\"" << log_target << ", " << symlink_location
+                                   << ") failed");
+    }
   }
-  assembly_dir_parent =
-      assembly_dir_parent.substr(0, FLAGS_assembly_dir.rfind('/'));
-  auto log =
-      SharedFD::Open(
-          assembly_dir_parent,
-          O_WRONLY | O_TMPFILE,
-          S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+
+  std::stringstream legacy_instance_path_stream;
+  legacy_instance_path_stream << FLAGS_instance_dir;
+  if (gflags::GetCommandLineFlagInfoOrDie("instance_dir").is_default) {
+    legacy_instance_path_stream << "_runtime";
+  }
+  legacy_instance_path_stream << "." << instance.id();
+  auto legacy_instance_path = legacy_instance_path_stream.str();
+
+  if (DirectoryExists(legacy_instance_path, /* follow_symlinks */ false)) {
+    CF_EXPECT(RecursivelyRemoveDirectory(legacy_instance_path),
+              "Failed to remove legacy directory " << legacy_instance_path);
+  } else if (FileExists(legacy_instance_path, /* follow_symlinks */ false)) {
+    CF_EXPECT(RemoveFile(legacy_instance_path),
+              "Failed to remove instance_dir symlink " << legacy_instance_path);
+  }
+  if (symlink(instance.instance_dir().c_str(), legacy_instance_path.c_str())) {
+    return CF_ERRNO("symlink(\"" << instance.instance_dir() << "\", \""
+                                 << legacy_instance_path << "\") failed");
+  }
+  return {};
+}
+
+Result<const CuttlefishConfig*> InitFilesystemAndCreateConfig(
+    FetcherConfig fetcher_config, KernelConfig kernel_config,
+    fruit::Injector<>& injector) {
+  std::string runtime_dir_parent = AbsolutePath(FLAGS_instance_dir);
+  while (runtime_dir_parent[runtime_dir_parent.size() - 1] == '/') {
+    runtime_dir_parent =
+        runtime_dir_parent.substr(0, FLAGS_instance_dir.rfind('/'));
+  }
+  runtime_dir_parent =
+      runtime_dir_parent.substr(0, FLAGS_instance_dir.rfind('/'));
+  auto log = SharedFD::Open(runtime_dir_parent, O_WRONLY | O_TMPFILE,
+                            S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
   if (!log->IsOpen()) {
     LOG(ERROR) << "Could not open O_TMPFILE precursor to assemble_cvd.log: "
                << log->StrError();
@@ -129,15 +158,19 @@
     // SaveConfig line below. Don't launch cuttlefish subprocesses between these
     // two operations, as those will assume they can read the config object from
     // disk.
-    auto config = InitializeCuttlefishConfiguration(
-        FLAGS_instance_dir, FLAGS_modem_simulator_count, kernel_config);
+    auto config = InitializeCuttlefishConfiguration(FLAGS_instance_dir,
+                                                    FLAGS_modem_simulator_count,
+                                                    kernel_config, injector);
     std::set<std::string> preserving;
-    if (FLAGS_resume && ShouldCreateAllCompositeDisks(config)) {
+    auto os_builder = OsCompositeDiskBuilder(config);
+    bool creating_os_disk = CF_EXPECT(os_builder.WillRebuildCompositeDisk());
+    if (FLAGS_resume && creating_os_disk) {
       LOG(INFO) << "Requested resuming a previous session (the default behavior) "
                 << "but the base images have changed under the overlay, making the "
                 << "overlay incompatible. Wiping the overlay files.";
-    } else if (FLAGS_resume && !ShouldCreateAllCompositeDisks(config)) {
+    } else if (FLAGS_resume && !creating_os_disk) {
       preserving.insert("overlay.img");
+      preserving.insert("ap_overlay.img");
       preserving.insert("os_composite_disk_config.txt");
       preserving.insert("os_composite_gpt_header.img");
       preserving.insert("os_composite_gpt_footer.img");
@@ -146,6 +179,7 @@
       preserving.insert("boot_repacked.img");
       preserving.insert("vendor_boot_repacked.img");
       preserving.insert("access-kregistry");
+      preserving.insert("hwcomposer-pmem");
       preserving.insert("NVChip");
       preserving.insert("gatekeeper_secure");
       preserving.insert("gatekeeper_insecure");
@@ -165,40 +199,74 @@
         ss.str("");
       }
     }
-    CHECK(CleanPriorFiles(preserving, FLAGS_assembly_dir, FLAGS_instance_dir))
-        << "Failed to clean prior files";
+    CF_EXPECT(CleanPriorFiles(preserving, config.assembly_dir(),
+                              config.instance_dirs()),
+              "Failed to clean prior files");
 
-    // Create assembly directory if it doesn't exist.
-    CHECK(EnsureDirectoryExists(FLAGS_assembly_dir));
+    CF_EXPECT(EnsureDirectoryExists(config.root_dir()));
+    CF_EXPECT(EnsureDirectoryExists(config.assembly_dir()));
+    CF_EXPECT(EnsureDirectoryExists(config.instances_dir()));
     if (log->LinkAtCwd(config.AssemblyPath("assemble_cvd.log"))) {
       LOG(ERROR) << "Unable to persist assemble_cvd log at "
                   << config.AssemblyPath("assemble_cvd.log")
                   << ": " << log->StrError();
     }
+
+    auto disk_config = GetOsCompositeDiskConfig();
+    if (auto it = std::find_if(disk_config.begin(), disk_config.end(),
+                               [](const auto& partition) {
+                                 return partition.label == "ap_rootfs";
+                               });
+        it != disk_config.end()) {
+      auto ap_image_idx = std::distance(disk_config.begin(), it) + 1;
+      std::stringstream ss;
+      ss << "/dev/vda" << ap_image_idx;
+      config.set_ap_image_dev_path(ss.str());
+    }
     for (const auto& instance : config.Instances()) {
       // Create instance directory if it doesn't exist.
-      CHECK(EnsureDirectoryExists(instance.instance_dir()));
+      CF_EXPECT(EnsureDirectoryExists(instance.instance_dir()));
       auto internal_dir = instance.instance_dir() + "/" + kInternalDirName;
-      CHECK(EnsureDirectoryExists(internal_dir));
+      CF_EXPECT(EnsureDirectoryExists(internal_dir));
       auto shared_dir = instance.instance_dir() + "/" + kSharedDirName;
-      CHECK(EnsureDirectoryExists(shared_dir));
+      CF_EXPECT(EnsureDirectoryExists(shared_dir));
       auto recording_dir = instance.instance_dir() + "/recording";
-      CHECK(EnsureDirectoryExists(recording_dir));
+      CF_EXPECT(EnsureDirectoryExists(recording_dir));
+      CF_EXPECT(EnsureDirectoryExists(instance.PerInstanceLogPath("")));
+      // TODO(schuffelen): Move this code somewhere better
+      CF_EXPECT(CreateLegacySymlinks(instance));
     }
-    CHECK(SaveConfig(config)) << "Failed to initialize configuration";
+    CF_EXPECT(SaveConfig(config), "Failed to initialize configuration");
   }
 
-  std::string first_instance = FLAGS_instance_dir + "." + std::to_string(GetInstance());
-  CHECK_EQ(symlink(first_instance.c_str(), FLAGS_instance_dir.c_str()), 0)
-      << "Could not symlink \"" << first_instance << "\" to \"" << FLAGS_instance_dir << "\"";
-
   // Do this early so that the config object is ready for anything that needs it
   auto config = CuttlefishConfig::Get();
-  CHECK(config) << "Failed to obtain config singleton";
+  CF_EXPECT(config != nullptr, "Failed to obtain config singleton");
 
-  ValidateAdbModeFlag(*config);
+  if (DirectoryExists(FLAGS_assembly_dir, /* follow_symlinks */ false)) {
+    CF_EXPECT(RecursivelyRemoveDirectory(FLAGS_assembly_dir),
+              "Failed to remove directory " << FLAGS_assembly_dir);
+  } else if (FileExists(FLAGS_assembly_dir, /* follow_symlinks */ false)) {
+    CF_EXPECT(RemoveFile(FLAGS_assembly_dir),
+              "Failed to remove file" << FLAGS_assembly_dir);
+  }
+  if (symlink(config->assembly_dir().c_str(), FLAGS_assembly_dir.c_str())) {
+    return CF_ERRNO("symlink(\"" << config->assembly_dir() << "\", \""
+                                 << FLAGS_assembly_dir << "\") failed");
+  }
 
-  CreateDynamicDiskFiles(fetcher_config, config);
+  std::string first_instance = config->Instances()[0].instance_dir();
+  std::string double_legacy_instance_dir = FLAGS_instance_dir + "_runtime";
+  if (FileExists(double_legacy_instance_dir, /* follow_symlinks */ false)) {
+    CF_EXPECT(RemoveFile(double_legacy_instance_dir),
+              "Failed to remove symlink " << double_legacy_instance_dir);
+  }
+  if (symlink(first_instance.c_str(), double_legacy_instance_dir.c_str())) {
+    return CF_ERRNO("symlink(\"" << first_instance << "\", \""
+                                 << double_legacy_instance_dir << "\") failed");
+  }
+
+  CF_EXPECT(CreateDynamicDiskFiles(fetcher_config, *config));
 
   return config;
 }
@@ -218,27 +286,38 @@
   SetCommandLineOptionWithMode("initramfs_path", discovered_ramdisk.c_str(),
                                google::FlagSettingMode::SET_FLAGS_DEFAULT);
 }
+
+fruit::Component<> FlagsComponent() {
+  return fruit::createComponent()
+      .install(AdbConfigComponent)
+      .install(AdbConfigFlagComponent)
+      .install(AdbConfigFragmentComponent)
+      .install(GflagsComponent)
+      .install(ConfigFlagComponent)
+      .install(CustomActionsComponent);
+}
+
 } // namespace
 
-int AssembleCvdMain(int argc, char** argv) {
+Result<int> AssembleCvdMain(int argc, char** argv) {
   setenv("ANDROID_LOG_TAGS", "*:v", /* overwrite */ 0);
   ::android::base::InitLogging(argv, android::base::StderrLogger);
 
   int tty = isatty(0);
   int error_num = errno;
-  CHECK_EQ(tty, 0)
-      << "stdin was a tty, expected to be passed the output of a previous stage. "
-      << "Did you mean to run launch_cvd?";
-  CHECK(error_num != EBADF)
-      << "stdin was not a valid file descriptor, expected to be passed the output "
-      << "of launch_cvd. Did you mean to run launch_cvd?";
+  CF_EXPECT(tty == 0,
+            "stdin was a tty, expected to be passed the output of a "
+            "previous stage. Did you mean to run launch_cvd?");
+  CF_EXPECT(error_num != EBADF,
+            "stdin was not a valid file descriptor, expected to be "
+            "passed the output of launch_cvd. Did you mean to run launch_cvd?");
 
   std::string input_files_str;
   {
     auto input_fd = SharedFD::Dup(0);
     auto bytes_read = ReadAll(input_fd, &input_files_str);
-    CHECK(bytes_read >= 0)
-        << "Failed to read input files. Error was \"" << input_fd->StrError() << "\"";
+    CF_EXPECT(bytes_read >= 0, "Failed to read input files. Error was \""
+                                   << input_fd->StrError() << "\"");
   }
   std::vector<std::string> input_files = android::base::Split(input_files_str, "\n");
 
@@ -246,11 +325,54 @@
   // set gflags defaults to point to kernel/RD from fetcher config
   ExtractKernelParamsFromFetcherConfig(fetcher_config);
 
-  KernelConfig kernel_config;
-  CHECK(ParseCommandLineFlags(&argc, &argv, &kernel_config)) << "Failed to parse arguments";
+  auto args = ArgsToVec(argc - 1, argv + 1);
+
+  bool help = false;
+  std::string help_str;
+  bool helpxml = false;
+
+  std::vector<Flag> help_flags = {
+      GflagsCompatFlag("help", help),
+      GflagsCompatFlag("helpfull", help),
+      GflagsCompatFlag("helpshort", help),
+      GflagsCompatFlag("helpmatch", help_str),
+      GflagsCompatFlag("helpon", help_str),
+      GflagsCompatFlag("helppackage", help_str),
+      GflagsCompatFlag("helpxml", helpxml),
+  };
+  for (const auto& help_flag : help_flags) {
+    if (!help_flag.Parse(args)) {
+      LOG(ERROR) << "Failed to process help flag.";
+      return 1;
+    }
+  }
+
+  fruit::Injector<> injector(FlagsComponent);
+  auto flag_features = injector.getMultibindings<FlagFeature>();
+  CF_EXPECT(FlagFeature::ProcessFlags(flag_features, args),
+            "Failed to parse flags.");
+
+  if (help || help_str != "") {
+    LOG(WARNING) << "TODO(schuffelen): Implement `--help` for assemble_cvd.";
+    LOG(WARNING) << "In the meantime, call `launch_cvd --help`";
+    return 1;
+  } else if (helpxml) {
+    if (!FlagFeature::WriteGflagsHelpXml(flag_features, std::cout)) {
+      LOG(ERROR) << "Failure in writing gflags helpxml output";
+    }
+    std::exit(1);  // For parity with gflags
+  }
+  // TODO(schuffelen): Put in "unknown flag" guards after gflags is removed.
+  // gflags either consumes all arguments that start with - or leaves all of
+  // them in place, and either errors out on unknown flags or accepts any flags.
+
+  auto kernel_config =
+      CF_EXPECT(GetKernelConfigAndSetDefaults(), "Failed to parse arguments");
 
   auto config =
-      InitFilesystemAndCreateConfig(std::move(fetcher_config), kernel_config);
+      CF_EXPECT(InitFilesystemAndCreateConfig(std::move(fetcher_config),
+                                              kernel_config, injector),
+                "Failed to create config");
 
   std::cout << GetConfigFilePath(*config) << "\n";
   std::cout << std::flush;
@@ -261,5 +383,7 @@
 } // namespace cuttlefish
 
 int main(int argc, char** argv) {
-  return cuttlefish::AssembleCvdMain(argc, argv);
+  auto res = cuttlefish::AssembleCvdMain(argc, argv);
+  CHECK(res.ok()) << "assemble_cvd failed: \n" << res.error();
+  return *res;
 }
diff --git a/host/commands/assemble_cvd/boot_config.cc b/host/commands/assemble_cvd/boot_config.cc
index b433940..7c9f8b1 100644
--- a/host/commands/assemble_cvd/boot_config.cc
+++ b/host/commands/assemble_cvd/boot_config.cc
@@ -28,7 +28,9 @@
 
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
+#include "common/libs/utils/size_utils.h"
 #include "common/libs/utils/subprocess.h"
+#include "host/libs/config/bootconfig_args.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/kernel_args.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
@@ -39,38 +41,38 @@
 DECLARE_bool(pause_in_bootloader);
 DECLARE_string(vm_manager);
 
+// Taken from external/avb/avbtool.py; this define is not in the headers
+#define MAX_AVB_METADATA_SIZE 69632ul
+
 namespace cuttlefish {
 namespace {
 
 size_t WriteEnvironment(const CuttlefishConfig& config,
-                        const std::vector<std::string>& kernel_args,
+                        const std::string& kernel_args,
                         const std::string& env_path) {
   std::ostringstream env;
-  env << "bootargs=" << android::base::Join(kernel_args, " ") << '\0';
-  if (!config.boot_slot().empty()) {
-      env << "android_slot_suffix=_" << config.boot_slot() << '\0';
-  }
 
-  if(FLAGS_pause_in_bootloader) {
-    env << "bootdelay=-1" << '\0';
+  if (!kernel_args.empty()) {
+    env << "uenvcmd=setenv bootargs \"$cbootargs " << kernel_args << "\" && ";
   } else {
-    env << "bootdelay=0" << '\0';
+    env << "uenvcmd=setenv bootargs \"$cbootargs\" && ";
   }
-
-  // Note that the 0 index points to the GPT table.
-  env << "bootcmd=boot_android virtio 0#misc" << '\0';
-  if (FLAGS_vm_manager == CrosvmManager::name() &&
-      config.target_arch() == Arch::Arm64) {
-    env << "fdtaddr=0x80000000" << '\0';
+  if (FLAGS_pause_in_bootloader) {
+    env << "if test $paused -ne 1; then paused=1; else run bootcmd_android; fi";
   } else {
-    env << "fdtaddr=0x40000000" << '\0';
+    env << "run bootcmd_android";
   }
   env << '\0';
+  if (!config.boot_slot().empty()) {
+    env << "android_slot_suffix=_" << config.boot_slot() << '\0';
+  }
+  env << '\0';
+
   std::string env_str = env.str();
   std::ofstream file_out(env_path.c_str(), std::ios::binary);
   file_out << env_str;
 
-  if(!file_out.good()) {
+  if (!file_out.good()) {
     return 0;
   }
 
@@ -79,42 +81,113 @@
 
 }  // namespace
 
+class InitBootloaderEnvPartitionImpl : public InitBootloaderEnvPartition {
+ public:
+  INJECT(InitBootloaderEnvPartitionImpl(
+      const CuttlefishConfig& config,
+      const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
 
-bool InitBootloaderEnvPartition(const CuttlefishConfig& config,
-                                const CuttlefishConfig::InstanceSpecific& instance) {
-  auto boot_env_image_path = instance.uboot_env_image_path();
-  auto tmp_boot_env_image_path = boot_env_image_path + ".tmp";
-  auto uboot_env_path = instance.PerInstancePath("mkenvimg_input");
-  auto kernel_args = KernelCommandLineFromConfig(config);
-  if(!WriteEnvironment(config, kernel_args, uboot_env_path)) {
-    LOG(ERROR) << "Unable to write out plaintext env '" << uboot_env_path << ".'";
-    return false;
-  }
+  // SetupFeature
+  std::string Name() const override { return "InitBootloaderEnvPartitionImpl"; }
+  bool Enabled() const override { return !config_.protected_vm(); }
 
-  auto mkimage_path = HostBinaryPath("mkenvimage");
-  Command cmd(mkimage_path);
-  cmd.AddParameter("-s");
-  cmd.AddParameter("4096");
-  cmd.AddParameter("-o");
-  cmd.AddParameter(tmp_boot_env_image_path);
-  cmd.AddParameter(uboot_env_path);
-  int success = cmd.Start().Wait();
-  if (success != 0) {
-    LOG(ERROR) << "Unable to run mkenvimage. Exited with status " << success;
-    return false;
-  }
-
-  if(!FileExists(boot_env_image_path) || ReadFile(boot_env_image_path) != ReadFile(tmp_boot_env_image_path)) {
-    if(!RenameFile(tmp_boot_env_image_path, boot_env_image_path)) {
-      LOG(ERROR) << "Unable to delete the old env image.";
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override {
+    auto boot_env_image_path = instance_.uboot_env_image_path();
+    auto tmp_boot_env_image_path = boot_env_image_path + ".tmp";
+    auto uboot_env_path = instance_.PerInstancePath("mkenvimg_input");
+    auto kernel_cmdline =
+        android::base::Join(KernelCommandLineFromConfig(config_), " ");
+    // If the bootconfig isn't supported in the guest kernel, the bootconfig
+    // args need to be passed in via the uboot env. This won't be an issue for
+    // protect kvm which is running a kernel with bootconfig support.
+    if (!config_.bootconfig_supported()) {
+      auto bootconfig_args = android::base::Join(
+          BootconfigArgsFromConfig(config_, instance_), " ");
+      // "androidboot.hardware" kernel parameter has changed to "hardware" in
+      // bootconfig and needs to be replaced before being used in the kernel
+      // cmdline.
+      bootconfig_args = android::base::StringReplace(
+          bootconfig_args, " hardware=", " androidboot.hardware=", true);
+      // TODO(b/182417593): Until we pass the module parameters through
+      // modules.options, we pass them through bootconfig using
+      // 'kernel.<key>=<value>' But if we don't support bootconfig, we need to
+      // rename them back to the old cmdline version
+      bootconfig_args =
+          android::base::StringReplace(bootconfig_args, " kernel.", " ", true);
+      kernel_cmdline += " ";
+      kernel_cmdline += bootconfig_args;
+    }
+    if (!WriteEnvironment(config_, kernel_cmdline, uboot_env_path)) {
+      LOG(ERROR) << "Unable to write out plaintext env '" << uboot_env_path
+                 << ".'";
       return false;
     }
-    LOG(DEBUG) << "Updated bootloader environment image.";
-  } else {
-    RemoveFile(tmp_boot_env_image_path);
+
+    auto mkimage_path = HostBinaryPath("mkenvimage_slim");
+    Command cmd(mkimage_path);
+    cmd.AddParameter("-output_path");
+    cmd.AddParameter(tmp_boot_env_image_path);
+    cmd.AddParameter("-input_path");
+    cmd.AddParameter(uboot_env_path);
+    int success = cmd.Start().Wait();
+    if (success != 0) {
+      LOG(ERROR) << "Unable to run mkenvimage_slim. Exited with status "
+                 << success;
+      return false;
+    }
+
+    const off_t boot_env_size_bytes = AlignToPowerOf2(
+        MAX_AVB_METADATA_SIZE + 4096, PARTITION_SIZE_SHIFT);
+
+    auto avbtool_path = HostBinaryPath("avbtool");
+    Command boot_env_hash_footer_cmd(avbtool_path);
+    boot_env_hash_footer_cmd.AddParameter("add_hash_footer");
+    boot_env_hash_footer_cmd.AddParameter("--image");
+    boot_env_hash_footer_cmd.AddParameter(tmp_boot_env_image_path);
+    boot_env_hash_footer_cmd.AddParameter("--partition_size");
+    boot_env_hash_footer_cmd.AddParameter(boot_env_size_bytes);
+    boot_env_hash_footer_cmd.AddParameter("--partition_name");
+    boot_env_hash_footer_cmd.AddParameter("uboot_env");
+    boot_env_hash_footer_cmd.AddParameter("--key");
+    boot_env_hash_footer_cmd.AddParameter(
+        DefaultHostArtifactsPath("etc/cvd_avb_testkey.pem"));
+    boot_env_hash_footer_cmd.AddParameter("--algorithm");
+    boot_env_hash_footer_cmd.AddParameter("SHA256_RSA4096");
+    success = boot_env_hash_footer_cmd.Start().Wait();
+    if (success != 0) {
+      LOG(ERROR) << "Unable to run append hash footer. Exited with status "
+                 << success;
+      return false;
+    }
+
+    if (!FileExists(boot_env_image_path) ||
+        ReadFile(boot_env_image_path) != ReadFile(tmp_boot_env_image_path)) {
+      if (!RenameFile(tmp_boot_env_image_path, boot_env_image_path)) {
+        LOG(ERROR) << "Unable to delete the old env image.";
+        return false;
+      }
+      LOG(DEBUG) << "Updated bootloader environment image.";
+    } else {
+      RemoveFile(tmp_boot_env_image_path);
+    }
+
+    return true;
   }
 
-  return true;
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>,
+                 InitBootloaderEnvPartition>
+InitBootloaderEnvPartitionComponent() {
+  return fruit::createComponent()
+      .bind<InitBootloaderEnvPartition, InitBootloaderEnvPartitionImpl>()
+      .addMultibinding<SetupFeature, InitBootloaderEnvPartition>();
 }
 
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/boot_config.h b/host/commands/assemble_cvd/boot_config.h
index 1a81b79..d89c922 100644
--- a/host/commands/assemble_cvd/boot_config.h
+++ b/host/commands/assemble_cvd/boot_config.h
@@ -16,11 +16,19 @@
 #pragma once
 
 #include <string>
-#include <host/libs/config/cuttlefish_config.h>
+
+#include <fruit/fruit.h>
+
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
 
 namespace cuttlefish {
 
-bool InitBootloaderEnvPartition(const CuttlefishConfig& config,
-                                const CuttlefishConfig::InstanceSpecific& instance);
+class InitBootloaderEnvPartition : public SetupFeature {};
+
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>,
+                 InitBootloaderEnvPartition>
+InitBootloaderEnvPartitionComponent();
 
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/boot_image_utils.cc b/host/commands/assemble_cvd/boot_image_utils.cc
index cfad23f..ec9e93f 100644
--- a/host/commands/assemble_cvd/boot_image_utils.cc
+++ b/host/commands/assemble_cvd/boot_image_utils.cc
@@ -71,32 +71,6 @@
   return true;
 }
 
-bool UnpackBootImage(const std::string& boot_image_path,
-                     const std::string& unpack_dir) {
-  auto unpack_path = HostBinaryPath("unpack_bootimg");
-  Command unpack_cmd(unpack_path);
-  unpack_cmd.AddParameter("--boot_img");
-  unpack_cmd.AddParameter(boot_image_path);
-  unpack_cmd.AddParameter("--out");
-  unpack_cmd.AddParameter(unpack_dir);
-
-  auto output_file = SharedFD::Creat(unpack_dir + "/boot_params", 0666);
-  if (!output_file->IsOpen()) {
-    LOG(ERROR) << "Unable to create intermediate boot params file: "
-               << output_file->StrError();
-    return false;
-  }
-  unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
-
-  int success = unpack_cmd.Start().Wait();
-  if (success != 0) {
-    LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status "
-               << success;
-    return false;
-  }
-  return true;
-}
-
 void RepackVendorRamdisk(const std::string& kernel_modules_ramdisk_path,
                          const std::string& original_ramdisk_path,
                          const std::string& new_ramdisk_path,
@@ -141,6 +115,34 @@
   final_rd << ramdisk_a.rdbuf() << ramdisk_b.rdbuf();
 }
 
+}  // namespace
+
+bool UnpackBootImage(const std::string& boot_image_path,
+                     const std::string& unpack_dir) {
+  auto unpack_path = HostBinaryPath("unpack_bootimg");
+  Command unpack_cmd(unpack_path);
+  unpack_cmd.AddParameter("--boot_img");
+  unpack_cmd.AddParameter(boot_image_path);
+  unpack_cmd.AddParameter("--out");
+  unpack_cmd.AddParameter(unpack_dir);
+
+  auto output_file = SharedFD::Creat(unpack_dir + "/boot_params", 0666);
+  if (!output_file->IsOpen()) {
+    LOG(ERROR) << "Unable to create intermediate boot params file: "
+               << output_file->StrError();
+    return false;
+  }
+  unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
+
+  int success = unpack_cmd.Start().Wait();
+  if (success != 0) {
+    LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status "
+               << success;
+    return false;
+  }
+  return true;
+}
+
 bool UnpackVendorBootImageIfNotUnpacked(
     const std::string& vendor_boot_image_path, const std::string& unpack_dir) {
   // the vendor boot params file is created during the first unpack. If it's
@@ -189,8 +191,6 @@
   return true;
 }
 
-}  // namespace
-
 bool RepackBootImage(const std::string& new_kernel_path,
                      const std::string& boot_image_path,
                      const std::string& new_boot_image_path,
@@ -222,11 +222,20 @@
     return false;
   }
 
-  auto fd = SharedFD::Open(tmp_boot_image_path, O_RDWR);
-  auto original_size = FileSize(boot_image_path);
-  CHECK(fd->Truncate(original_size) == 0)
-    << "`truncate --size=" << original_size << " " << tmp_boot_image_path << "` "
-    << "failed: " << fd->StrError();
+  auto avbtool_path = HostBinaryPath("avbtool");
+  Command avb_cmd(avbtool_path);
+  avb_cmd.AddParameter("add_hash_footer");
+  avb_cmd.AddParameter("--image");
+  avb_cmd.AddParameter(tmp_boot_image_path);
+  avb_cmd.AddParameter("--partition_size");
+  avb_cmd.AddParameter(FileSize(boot_image_path));
+  avb_cmd.AddParameter("--partition_name");
+  avb_cmd.AddParameter("boot");
+  success = avb_cmd.Start().Wait();
+  if (success != 0) {
+    LOG(ERROR) << "Unable to run avbtool. Exited with status " << success;
+    return false;
+  }
 
   return DeleteTmpFileIfNotChanged(tmp_boot_image_path, new_boot_image_path);
 }
@@ -235,8 +244,6 @@
                            const std::string& vendor_boot_image_path,
                            const std::string& new_vendor_boot_image_path,
                            const std::string& unpack_dir,
-                           const std::string& repack_dir,
-                           const std::vector<std::string>& bootconfig_args,
                            bool bootconfig_supported) {
   if (UnpackVendorBootImageIfNotUnpacked(vendor_boot_image_path, unpack_dir) ==
       false) {
@@ -255,23 +262,15 @@
     ramdisk_path = unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK;
   }
 
-  auto bootconfig_fd = SharedFD::Creat(repack_dir + "/bootconfig", 0666);
-  if (!bootconfig_fd->IsOpen()) {
-    LOG(ERROR) << "Unable to create intermediate bootconfig file: "
-               << bootconfig_fd->StrError();
-    return false;
-  }
   std::string bootconfig = ReadFile(unpack_dir + "/bootconfig");
-  bootconfig_fd->Write(bootconfig.c_str(), bootconfig.size());
   LOG(DEBUG) << "Bootconfig parameters from vendor boot image are "
-             << ReadFile(repack_dir + "/bootconfig");
+             << bootconfig;
   std::string vendor_boot_params = ReadFile(unpack_dir + "/vendor_boot_params");
   auto kernel_cmdline =
       ExtractValue(vendor_boot_params, "vendor command line args: ") +
       (bootconfig_supported
            ? ""
-           : " " + android::base::StringReplace(bootconfig, "\n", " ", true) +
-                 " " + android::base::Join(bootconfig_args, " "));
+           : " " + android::base::StringReplace(bootconfig, "\n", " ", true));
   if (!bootconfig_supported) {
     // TODO(b/182417593): Until we pass the module parameters through
     // modules.options, we pass them through bootconfig using
@@ -280,8 +279,7 @@
     kernel_cmdline = android::base::StringReplace(
         kernel_cmdline, " kernel.", " ", true);
   }
-  LOG(DEBUG) << "Cmdline from vendor boot image and config is "
-             << kernel_cmdline;
+  LOG(DEBUG) << "Cmdline from vendor boot image is " << kernel_cmdline;
 
   auto tmp_vendor_boot_image_path = new_vendor_boot_image_path + TMP_EXTENSION;
   auto repack_path = HostBinaryPath("mkbootimg");
@@ -298,7 +296,7 @@
   repack_cmd.AddParameter(unpack_dir + "/dtb");
   if (bootconfig_supported) {
     repack_cmd.AddParameter("--vendor_bootconfig");
-    repack_cmd.AddParameter(repack_dir + "/bootconfig");
+    repack_cmd.AddParameter(unpack_dir + "/bootconfig");
   }
 
   int success = repack_cmd.Start().Wait();
@@ -307,11 +305,20 @@
     return false;
   }
 
-  auto fd = SharedFD::Open(tmp_vendor_boot_image_path, O_RDWR);
-  auto original_size = FileSize(vendor_boot_image_path);
-  CHECK(fd->Truncate(original_size) == 0)
-    << "`truncate --size=" << original_size << " " << tmp_vendor_boot_image_path << "` "
-    << "failed: " << fd->StrError();
+  auto avbtool_path = HostBinaryPath("avbtool");
+  Command avb_cmd(avbtool_path);
+  avb_cmd.AddParameter("add_hash_footer");
+  avb_cmd.AddParameter("--image");
+  avb_cmd.AddParameter(tmp_vendor_boot_image_path);
+  avb_cmd.AddParameter("--partition_size");
+  avb_cmd.AddParameter(FileSize(vendor_boot_image_path));
+  avb_cmd.AddParameter("--partition_name");
+  avb_cmd.AddParameter("vendor_boot");
+  success = avb_cmd.Start().Wait();
+  if (success != 0) {
+    LOG(ERROR) << "Unable to run avbtool. Exited with status " << success;
+    return false;
+  }
 
   return DeleteTmpFileIfNotChanged(tmp_vendor_boot_image_path, new_vendor_boot_image_path);
 }
@@ -319,14 +326,75 @@
 bool RepackVendorBootImageWithEmptyRamdisk(
     const std::string& vendor_boot_image_path,
     const std::string& new_vendor_boot_image_path,
-    const std::string& unpack_dir, const std::string& repack_dir,
-    const std::vector<std::string>& bootconfig_args,
-    bool bootconfig_supported) {
+    const std::string& unpack_dir, bool bootconfig_supported) {
   auto empty_ramdisk_file =
       SharedFD::Creat(unpack_dir + "/empty_ramdisk", 0666);
   return RepackVendorBootImage(
       unpack_dir + "/empty_ramdisk", vendor_boot_image_path,
-      new_vendor_boot_image_path, unpack_dir, repack_dir, bootconfig_args,
-      bootconfig_supported);
+      new_vendor_boot_image_path, unpack_dir, bootconfig_supported);
+}
+
+void RepackGem5BootImage(const std::string& initrd_path,
+                         const std::string& bootconfig_path,
+                         const std::string& unpack_dir) {
+  // Simulate per-instance what the bootloader would usually do
+  // Since on other devices this runs every time, just do it here every time
+  std::ofstream final_rd(initrd_path,
+                         std::ios_base::binary | std::ios_base::trunc);
+
+  std::ifstream boot_ramdisk(unpack_dir + "/ramdisk",
+                             std::ios_base::binary);
+  std::ifstream vendor_boot_ramdisk(unpack_dir +
+                                    "/concatenated_vendor_ramdisk",
+                                    std::ios_base::binary);
+
+  std::ifstream vendor_boot_bootconfig(unpack_dir + "/bootconfig",
+                                       std::ios_base::binary |
+                                       std::ios_base::ate);
+
+  auto vb_size = vendor_boot_bootconfig.tellg();
+  vendor_boot_bootconfig.seekg(0);
+
+  std::ifstream persistent_bootconfig(bootconfig_path,
+                                      std::ios_base::binary |
+                                      std::ios_base::ate);
+
+  auto pb_size = persistent_bootconfig.tellg();
+  persistent_bootconfig.seekg(0);
+
+  // Build the bootconfig string, trim it, and write the length, checksum
+  // and trailer bytes
+
+  std::string bootconfig =
+    "androidboot.slot_suffix=_a\n"
+    "androidboot.force_normal_boot=1\n"
+    "androidboot.verifiedbootstate=orange\n";
+  auto bootconfig_size = bootconfig.size();
+  bootconfig.resize(bootconfig_size + (uint64_t)(vb_size + pb_size), '\0');
+  vendor_boot_bootconfig.read(&bootconfig[bootconfig_size], vb_size);
+  persistent_bootconfig.read(&bootconfig[bootconfig_size + vb_size], pb_size);
+  // Trim the block size padding from the persistent bootconfig
+  bootconfig.erase(bootconfig.find_last_not_of('\0'));
+
+  // Write out the ramdisks and bootconfig blocks
+  final_rd << boot_ramdisk.rdbuf() << vendor_boot_ramdisk.rdbuf()
+           << bootconfig;
+
+  // Append bootconfig length
+  bootconfig_size = bootconfig.size();
+  final_rd.write(reinterpret_cast<const char *>(&bootconfig_size),
+                 sizeof(uint32_t));
+
+  // Append bootconfig checksum
+  uint32_t bootconfig_csum = 0;
+  for (auto i = 0; i < bootconfig_size; i++) {
+    bootconfig_csum += bootconfig[i];
+  }
+  final_rd.write(reinterpret_cast<const char *>(&bootconfig_csum),
+                 sizeof(uint32_t));
+
+  // Append bootconfig trailer
+  final_rd << "#BOOTCONFIG\n";
+  final_rd.close();
 }
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/boot_image_utils.h b/host/commands/assemble_cvd/boot_image_utils.h
index fafb514..c5a2d1b 100644
--- a/host/commands/assemble_cvd/boot_image_utils.h
+++ b/host/commands/assemble_cvd/boot_image_utils.h
@@ -27,12 +27,16 @@
                            const std::string& vendor_boot_image_path,
                            const std::string& new_vendor_boot_image_path,
                            const std::string& unpack_dir,
-                           const std::string& repack_dir,
-                           const std::vector<std::string>& bootconfig_args,
                            bool bootconfig_supported);
 bool RepackVendorBootImageWithEmptyRamdisk(
     const std::string& vendor_boot_image_path,
     const std::string& new_vendor_boot_image_path,
-    const std::string& unpack_dir, const std::string& repack_dir,
-    const std::vector<std::string>& bootconfig_args, bool bootconfig_supported);
+    const std::string& unpack_dir, bool bootconfig_supported);
+bool UnpackBootImage(const std::string& boot_image_path,
+                     const std::string& unpack_dir);
+bool UnpackVendorBootImageIfNotUnpacked(
+    const std::string& vendor_boot_image_path, const std::string& unpack_dir);
+void RepackGem5BootImage(const std::string& initrd_path,
+                         const std::string& bootconfig_path,
+                         const std::string& unpack_dir);
 }
diff --git a/host/commands/assemble_cvd/clean.cc b/host/commands/assemble_cvd/clean.cc
index 6197a8f..3a9637d 100644
--- a/host/commands/assemble_cvd/clean.cc
+++ b/host/commands/assemble_cvd/clean.cc
@@ -23,42 +23,40 @@
 #include <vector>
 
 #include <android-base/logging.h>
+#include <android-base/strings.h>
 
-#include "host/commands/assemble_cvd/flags.h"
 #include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
+#include "host/commands/assemble_cvd/flags.h"
 
 namespace cuttlefish {
 namespace {
 
-bool CleanPriorFiles(const std::string& path, const std::set<std::string>& preserving) {
+Result<void> CleanPriorFiles(const std::string& path,
+                             const std::set<std::string>& preserving) {
   if (preserving.count(cpp_basename(path))) {
     LOG(DEBUG) << "Preserving: " << path;
-    return true;
+    return {};
   }
   struct stat statbuf;
   if (lstat(path.c_str(), &statbuf) < 0) {
     int error_num = errno;
     if (error_num == ENOENT) {
-      return true;
+      return {};
     } else {
-      LOG(ERROR) << "Could not stat \"" << path << "\": " << strerror(error_num);
-      return false;
+      return CF_ERRNO("Could not stat \"" << path);
     }
   }
   if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
     LOG(DEBUG) << "Deleting: " << path;
     if (unlink(path.c_str()) < 0) {
-      int error_num = errno;
-      LOG(ERROR) << "Could not unlink \"" << path << "\", error was " << strerror(error_num);
-      return false;
+      return CF_ERRNO("Could not unlink \"" << path << "\"");
     }
-    return true;
+    return {};
   }
   std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(path.c_str()), closedir);
   if (!dir) {
-    int error_num = errno;
-    LOG(ERROR) << "Could not clean \"" << path << "\": error was " << strerror(error_num);
-    return false;
+    return CF_ERRNO("Could not clean \"" << path << "\"");
   }
   for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
     std::string entity_name(entity->d_name);
@@ -66,30 +64,28 @@
       continue;
     }
     std::string entity_path = path + "/" + entity_name;
-    if (!CleanPriorFiles(entity_path.c_str(), preserving)) {
-      return false;
-    }
+    CF_EXPECT(CleanPriorFiles(entity_path.c_str(), preserving),
+              "CleanPriorFiles for \""
+                  << path << "\" failed on recursing into \"" << entity_path
+                  << "\"");
   }
   if (rmdir(path.c_str()) < 0) {
     if (!(errno == EEXIST || errno == ENOTEMPTY)) {
       // If EEXIST or ENOTEMPTY, probably because a file was preserved
-      int error_num = errno;
-      LOG(ERROR) << "Could not rmdir \"" << path << "\", error was " << strerror(error_num);
-      return false;
+      return CF_ERRNO("Could not rmdir \"" << path << "\"");
     }
   }
-  return true;
+  return {};
 }
 
-bool CleanPriorFiles(const std::vector<std::string>& paths, const std::set<std::string>& preserving) {
+Result<void> CleanPriorFiles(const std::vector<std::string>& paths,
+                             const std::set<std::string>& preserving) {
   std::string prior_files;
   for (auto path : paths) {
     struct stat statbuf;
     if (stat(path.c_str(), &statbuf) < 0 && errno != ENOENT) {
       // If ENOENT, it doesn't exist yet, so there is no work to do'
-      int error_num = errno;
-      LOG(ERROR) << "Could not stat \"" << path << "\": " << strerror(error_num);
-      return false;
+      return CF_ERRNO("Could not stat \"" << path << "\"");
     }
     bool is_directory = (statbuf.st_mode & S_IFMT) == S_IFDIR;
     prior_files += (is_directory ? (path + "/*") : path) + " ";
@@ -98,25 +94,19 @@
   std::string lsof_cmd = "lsof -t " + prior_files + " >/dev/null 2>&1";
   int rval = std::system(lsof_cmd.c_str());
   // lsof returns 0 if any of the files are open
-  if (WEXITSTATUS(rval) == 0) {
-    LOG(ERROR) << "Clean aborted: files are in use";
-    return false;
-  }
+  CF_EXPECT(WEXITSTATUS(rval) != 0, "Clean aborted: files are in use");
   for (const auto& path : paths) {
-    if (!CleanPriorFiles(path, preserving)) {
-      LOG(ERROR) << "Remove of file under \"" << path << "\" failed";
-      return false;
-    }
+    CF_EXPECT(CleanPriorFiles(path, preserving),
+              "CleanPriorFiles failed for \"" << path << "\"");
   }
-  return true;
+  return {};
 }
 
 } // namespace
 
-bool CleanPriorFiles(
-    const std::set<std::string>& preserving,
-    const std::string& assembly_dir,
-    const std::string& instance_dir) {
+Result<void> CleanPriorFiles(const std::set<std::string>& preserving,
+                             const std::string& assembly_dir,
+                             const std::vector<std::string>& instance_dirs) {
   std::vector<std::string> paths = {
     // Everything in the assembly directory
     assembly_dir,
@@ -125,32 +115,13 @@
     // The global link to the config file
     GetGlobalConfigFileLink(),
   };
-
-  std::string runtime_dir_parent = cpp_dirname(AbsolutePath(instance_dir));
-  std::string runtime_dirs_basename = cpp_basename(AbsolutePath(instance_dir));
-
-  std::regex instance_dir_regex("^.+\\.[1-9]\\d*$");
-  for (const auto& path : DirectoryContents(runtime_dir_parent)) {
-    std::string absl_path = runtime_dir_parent + "/" + path;
-    if((path.rfind(runtime_dirs_basename, 0) == 0) && std::regex_match(path, instance_dir_regex) &&
-        DirectoryExists(absl_path)) {
-      paths.push_back(absl_path);
-    }
-  }
-  paths.push_back(instance_dir);
-  return CleanPriorFiles(paths, preserving);
-}
-
-bool EnsureDirectoryExists(const std::string& directory_path) {
-  if (!DirectoryExists(directory_path)) {
-    LOG(DEBUG) << "Setting up " << directory_path;
-    if (mkdir(directory_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
-        && errno != EEXIST) {
-      PLOG(ERROR) << "Failed to create dir: \"" << directory_path << "\" ";
-      return false;
-    }
-  }
-  return true;
+  paths.insert(paths.end(), instance_dirs.begin(), instance_dirs.end());
+  using android::base::Join;
+  CF_EXPECT(CleanPriorFiles(paths, preserving),
+            "CleanPriorFiles("
+                << "paths = {" << Join(paths, ", ") << "}, "
+                << "preserving = {" << Join(preserving, ", ") << "}) failed");
+  return {};
 }
 
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/clean.h b/host/commands/assemble_cvd/clean.h
index 1f33685..3097279 100644
--- a/host/commands/assemble_cvd/clean.h
+++ b/host/commands/assemble_cvd/clean.h
@@ -18,13 +18,12 @@
 #include <set>
 #include <string>
 
+#include "common/libs/utils/result.h"
+
 namespace cuttlefish {
 
-bool CleanPriorFiles(
-    const std::set<std::string>& preserving,
-    const std::string& assembly_dir,
-    const std::string& instance_dir);
-
-bool EnsureDirectoryExists(const std::string& directory_path);
+Result<void> CleanPriorFiles(const std::set<std::string>& preserving,
+                             const std::string& assembly_dir,
+                             const std::vector<std::string>& instance_dirs);
 
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/disk_builder.cpp b/host/commands/assemble_cvd/disk_builder.cpp
new file mode 100644
index 0000000..57ef039
--- /dev/null
+++ b/host/commands/assemble_cvd/disk_builder.cpp
@@ -0,0 +1,221 @@
+//
+// 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.
+
+#include "host/commands/assemble_cvd/disk_builder.h"
+
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/files.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/image_aggregator/image_aggregator.h"
+#include "host/libs/vm_manager/crosvm_manager.h"
+
+namespace cuttlefish {
+
+static std::chrono::system_clock::time_point LastUpdatedInputDisk(
+    const std::vector<ImagePartition>& partitions) {
+  std::chrono::system_clock::time_point ret;
+  for (auto& partition : partitions) {
+    if (partition.label == "frp") {
+      continue;
+    }
+
+    auto partition_mod_time = FileModificationTime(partition.image_file_path);
+    if (partition_mod_time > ret) {
+      ret = partition_mod_time;
+    }
+  }
+  return ret;
+}
+
+DiskBuilder& DiskBuilder::Partitions(std::vector<ImagePartition> partitions) & {
+  partitions_ = std::move(partitions);
+  return *this;
+}
+DiskBuilder DiskBuilder::Partitions(std::vector<ImagePartition> partitions) && {
+  partitions_ = std::move(partitions);
+  return *this;
+}
+
+DiskBuilder& DiskBuilder::HeaderPath(std::string header_path) & {
+  header_path_ = std::move(header_path);
+  return *this;
+}
+DiskBuilder DiskBuilder::HeaderPath(std::string header_path) && {
+  header_path_ = std::move(header_path);
+  return *this;
+}
+
+DiskBuilder& DiskBuilder::FooterPath(std::string footer_path) & {
+  footer_path_ = std::move(footer_path);
+  return *this;
+}
+DiskBuilder DiskBuilder::FooterPath(std::string footer_path) && {
+  footer_path_ = std::move(footer_path);
+  return *this;
+}
+
+DiskBuilder& DiskBuilder::CrosvmPath(std::string crosvm_path) & {
+  crosvm_path_ = std::move(crosvm_path);
+  return *this;
+}
+DiskBuilder DiskBuilder::CrosvmPath(std::string crosvm_path) && {
+  crosvm_path_ = std::move(crosvm_path);
+  return *this;
+}
+
+DiskBuilder& DiskBuilder::VmManager(std::string vm_manager) & {
+  vm_manager_ = std::move(vm_manager);
+  return *this;
+}
+DiskBuilder DiskBuilder::VmManager(std::string vm_manager) && {
+  vm_manager_ = std::move(vm_manager);
+  return *this;
+}
+
+DiskBuilder& DiskBuilder::ConfigPath(std::string config_path) & {
+  config_path_ = std::move(config_path);
+  return *this;
+}
+DiskBuilder DiskBuilder::ConfigPath(std::string config_path) && {
+  config_path_ = std::move(config_path);
+  return *this;
+}
+
+DiskBuilder& DiskBuilder::CompositeDiskPath(std::string composite_disk_path) & {
+  composite_disk_path_ = std::move(composite_disk_path);
+  return *this;
+}
+DiskBuilder DiskBuilder::CompositeDiskPath(std::string composite_disk_path) && {
+  composite_disk_path_ = std::move(composite_disk_path);
+  return *this;
+}
+
+DiskBuilder& DiskBuilder::OverlayPath(std::string overlay_path) & {
+  overlay_path_ = std::move(overlay_path);
+  return *this;
+}
+DiskBuilder DiskBuilder::OverlayPath(std::string overlay_path) && {
+  overlay_path_ = std::move(overlay_path);
+  return *this;
+}
+
+DiskBuilder& DiskBuilder::ResumeIfPossible(bool resume_if_possible) & {
+  resume_if_possible_ = resume_if_possible;
+  return *this;
+}
+DiskBuilder DiskBuilder::ResumeIfPossible(bool resume_if_possible) && {
+  resume_if_possible_ = resume_if_possible;
+  return *this;
+}
+
+Result<std::string> DiskBuilder::TextConfig() {
+  std::ostringstream disk_conf;
+
+  CF_EXPECT(!vm_manager_.empty(), "Missing vm_manager");
+  disk_conf << vm_manager_ << "\n";
+
+  CF_EXPECT(!partitions_.empty(), "No partitions");
+  for (auto& partition : partitions_) {
+    disk_conf << partition.image_file_path << "\n";
+  }
+  return disk_conf.str();
+}
+
+Result<bool> DiskBuilder::WillRebuildCompositeDisk() {
+  if (!resume_if_possible_) {
+    return true;
+  }
+
+  CF_EXPECT(!config_path_.empty(), "No config path");
+  if (ReadFile(config_path_) != CF_EXPECT(TextConfig())) {
+    LOG(DEBUG) << "Composite disk text config mismatch";
+    return true;
+  }
+
+  CF_EXPECT(!partitions_.empty(), "No partitions");
+  auto last_component_mod_time = LastUpdatedInputDisk(partitions_);
+
+  CF_EXPECT(!composite_disk_path_.empty(), "No composite disk path");
+  auto composite_mod_time = FileModificationTime(composite_disk_path_);
+
+  if (composite_mod_time == decltype(composite_mod_time)()) {
+    LOG(DEBUG) << "No prior composite disk";
+    return true;
+  } else if (last_component_mod_time > composite_mod_time) {
+    LOG(DEBUG) << "Composite disk component file updated";
+    return true;
+  }
+
+  return false;
+}
+
+Result<bool> DiskBuilder::BuildCompositeDiskIfNecessary() {
+  if (!CF_EXPECT(WillRebuildCompositeDisk())) {
+    return false;
+  }
+
+  CF_EXPECT(!vm_manager_.empty());
+  if (vm_manager_ == vm_manager::CrosvmManager::name()) {
+    CF_EXPECT(!header_path_.empty(), "No header path");
+    CF_EXPECT(!footer_path_.empty(), "No footer path");
+    CreateCompositeDisk(partitions_, AbsolutePath(header_path_),
+                        AbsolutePath(footer_path_),
+                        AbsolutePath(composite_disk_path_));
+  } else {
+    // If this doesn't fit into the disk, it will fail while aggregating. The
+    // aggregator doesn't maintain any sparse attributes.
+    AggregateImage(partitions_, AbsolutePath(composite_disk_path_));
+  }
+
+  using android::base::WriteStringToFile;
+  CF_EXPECT(WriteStringToFile(CF_EXPECT(TextConfig()), config_path_), true);
+
+  return true;
+}
+
+Result<bool> DiskBuilder::BuildOverlayIfNecessary() {
+  bool can_reuse_overlay = resume_if_possible_;
+
+  CF_EXPECT(!overlay_path_.empty(), "Overlay path missing");
+  auto overlay_mod_time = FileModificationTime(overlay_path_);
+
+  CF_EXPECT(!composite_disk_path_.empty(), "Composite disk path missing");
+  auto composite_disk_mod_time = FileModificationTime(composite_disk_path_);
+  if (overlay_mod_time == decltype(overlay_mod_time)()) {
+    LOG(DEBUG) << "No prior overlay";
+    can_reuse_overlay = false;
+  } else if (overlay_mod_time < composite_disk_mod_time) {
+    LOG(DEBUG) << "Overlay is out of date";
+    can_reuse_overlay = false;
+  }
+
+  if (can_reuse_overlay) {
+    return false;
+  }
+
+  CF_EXPECT(!crosvm_path_.empty(), "crosvm binary missing");
+  CreateQcowOverlay(crosvm_path_, composite_disk_path_, overlay_path_);
+
+  return true;
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/disk_builder.h b/host/commands/assemble_cvd/disk_builder.h
new file mode 100644
index 0000000..7a23a5a
--- /dev/null
+++ b/host/commands/assemble_cvd/disk_builder.h
@@ -0,0 +1,77 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <chrono>
+#include <string>
+#include <vector>
+
+#include "common/libs/utils/result.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/image_aggregator/image_aggregator.h"
+
+namespace cuttlefish {
+
+class DiskBuilder {
+ public:
+  DiskBuilder& Partitions(std::vector<ImagePartition> partitions) &;
+  DiskBuilder Partitions(std::vector<ImagePartition> partitions) &&;
+
+  DiskBuilder& HeaderPath(std::string header_path) &;
+  DiskBuilder HeaderPath(std::string header_path) &&;
+
+  DiskBuilder& FooterPath(std::string footer_path) &;
+  DiskBuilder FooterPath(std::string footer_path) &&;
+
+  DiskBuilder& CrosvmPath(std::string crosvm_path) &;
+  DiskBuilder CrosvmPath(std::string crosvm_path) &&;
+
+  DiskBuilder& VmManager(std::string vm_manager) &;
+  DiskBuilder VmManager(std::string vm_manager) &&;
+
+  DiskBuilder& ConfigPath(std::string config_path) &;
+  DiskBuilder ConfigPath(std::string config_path) &&;
+
+  DiskBuilder& CompositeDiskPath(std::string composite_disk_path) &;
+  DiskBuilder CompositeDiskPath(std::string composite_disk_path) &&;
+
+  DiskBuilder& OverlayPath(std::string overlay_path) &;
+  DiskBuilder OverlayPath(std::string overlay_path) &&;
+
+  DiskBuilder& ResumeIfPossible(bool resume_if_possible) &;
+  DiskBuilder ResumeIfPossible(bool resume_if_possible) &&;
+
+  Result<bool> WillRebuildCompositeDisk();
+  /** Returns `true` if the file was actually rebuilt. */
+  Result<bool> BuildCompositeDiskIfNecessary();
+  /** Returns `true` if the file was actually rebuilt. */
+  Result<bool> BuildOverlayIfNecessary();
+
+ private:
+  Result<std::string> TextConfig();
+
+  std::vector<ImagePartition> partitions_;
+  std::string header_path_;
+  std::string footer_path_;
+  std::string vm_manager_;
+  std::string crosvm_path_;
+  std::string config_path_;
+  std::string composite_disk_path_;
+  std::string overlay_path_;
+  bool resume_if_possible_;
+};
+
+}  // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/disk_flags.cc b/host/commands/assemble_cvd/disk_flags.cc
index e5ccac9..c06d7d7 100644
--- a/host/commands/assemble_cvd/disk_flags.cc
+++ b/host/commands/assemble_cvd/disk_flags.cc
@@ -16,14 +16,14 @@
 
 #include "host/commands/assemble_cvd/disk_flags.h"
 
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <fruit/fruit.h>
+#include <gflags/gflags.h>
 #include <sys/statvfs.h>
 
 #include <fstream>
 
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <gflags/gflags.h>
-
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
@@ -31,22 +31,28 @@
 #include "common/libs/utils/subprocess.h"
 #include "host/commands/assemble_cvd/boot_config.h"
 #include "host/commands/assemble_cvd/boot_image_utils.h"
+#include "host/commands/assemble_cvd/disk_builder.h"
 #include "host/commands/assemble_cvd/super_image_mixer.h"
 #include "host/libs/config/bootconfig_args.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/data_image.h"
-#include "host/libs/image_aggregator/image_aggregator.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
+#include "host/libs/vm_manager/gem5_manager.h"
 
 // Taken from external/avb/libavb/avb_slot_verify.c; this define is not in the headers
 #define VBMETA_MAX_SIZE 65536ul
+// Taken from external/avb/avbtool.py; this define is not in the headers
+#define MAX_AVB_METADATA_SIZE 69632ul
 
-DEFINE_string(system_image_dir, cuttlefish::DefaultGuestImagePath(""),
-              "Location of the system partition images.");
+DECLARE_string(system_image_dir);
 
 DEFINE_string(boot_image, "",
               "Location of cuttlefish boot image. If empty it is assumed to be "
               "boot.img in the directory specified by -system_image_dir.");
+DEFINE_string(
+    init_boot_image, "",
+    "Location of cuttlefish init boot image. If empty it is assumed to "
+    "be init_boot.img in the directory specified by -system_image_dir.");
 DEFINE_string(data_image, "", "Location of the data partition image.");
 DEFINE_string(super_image, "", "Location of the super partition image.");
 DEFINE_string(misc_image, "",
@@ -63,13 +69,23 @@
 DEFINE_string(vbmeta_system_image, "",
               "Location of cuttlefish vbmeta_system image. If empty it is assumed to "
               "be vbmeta_system.img in the directory specified by -system_image_dir.");
-DEFINE_string(esp, "", "Path to ESP partition image (FAT formatted)");
+DEFINE_string(otheros_esp_image, "",
+              "Location of cuttlefish esp image. If the image does not exist, "
+              "and --otheros_root_image is specified, an esp partition image "
+              "is created with default bootloaders.");
+DEFINE_string(otheros_kernel_path, "",
+              "Location of cuttlefish otheros kernel.");
+DEFINE_string(otheros_initramfs_path, "",
+              "Location of cuttlefish otheros initramfs.img.");
+DEFINE_string(otheros_root_image, "",
+              "Location of cuttlefish otheros root filesystem image.");
 
 DEFINE_int32(blank_metadata_image_mb, 16,
              "The size of the blank metadata image to generate, MB.");
 DEFINE_int32(blank_sdcard_image_mb, 2048,
              "If enabled, the size of the blank sdcard image to generate, MB.");
 
+DECLARE_string(ap_rootfs_image);
 DECLARE_string(bootloader);
 DECLARE_bool(use_sdcard);
 DECLARE_string(initramfs_path);
@@ -79,19 +95,22 @@
 
 namespace cuttlefish {
 
-using vm_manager::CrosvmManager;
+using vm_manager::Gem5Manager;
 
-bool ResolveInstanceFiles() {
-  if (FLAGS_system_image_dir.empty()) {
-    LOG(ERROR) << "--system_image_dir must be specified.";
-    return false;
-  }
+Result<void> ResolveInstanceFiles() {
+  CF_EXPECT(!FLAGS_system_image_dir.empty(),
+            "--system_image_dir must be specified.");
 
   // If user did not specify location of either of these files, expect them to
   // be placed in --system_image_dir location.
   std::string default_boot_image = FLAGS_system_image_dir + "/boot.img";
   SetCommandLineOptionWithMode("boot_image", default_boot_image.c_str(),
                                google::FlagSettingMode::SET_FLAGS_DEFAULT);
+  std::string default_init_boot_image =
+      FLAGS_system_image_dir + "/init_boot.img";
+  SetCommandLineOptionWithMode("init_boot_image",
+                               default_init_boot_image.c_str(),
+                               google::FlagSettingMode::SET_FLAGS_DEFAULT);
   std::string default_data_image = FLAGS_system_image_dir + "/userdata.img";
   SetCommandLineOptionWithMode("data_image", default_data_image.c_str(),
                                google::FlagSettingMode::SET_FLAGS_DEFAULT);
@@ -104,6 +123,9 @@
   std::string default_misc_image = FLAGS_system_image_dir + "/misc.img";
   SetCommandLineOptionWithMode("misc_image", default_misc_image.c_str(),
                                google::FlagSettingMode::SET_FLAGS_DEFAULT);
+  std::string default_esp_image = FLAGS_system_image_dir + "/esp.img";
+  SetCommandLineOptionWithMode("otheros_esp_image", default_esp_image.c_str(),
+                               google::FlagSettingMode::SET_FLAGS_DEFAULT);
   std::string default_vendor_boot_image = FLAGS_system_image_dir
                                         + "/vendor_boot.img";
   SetCommandLineOptionWithMode("vendor_boot_image",
@@ -118,84 +140,118 @@
                                default_vbmeta_system_image.c_str(),
                                google::FlagSettingMode::SET_FLAGS_DEFAULT);
 
-  return true;
+  return {};
 }
 
-std::vector<ImagePartition> os_composite_disk_config(
-    const CuttlefishConfig::InstanceSpecific& instance) {
+std::vector<ImagePartition> GetOsCompositeDiskConfig() {
   std::vector<ImagePartition> partitions;
-  partitions.push_back(ImagePartition {
-    .label = "misc",
-    .image_file_path = FLAGS_misc_image,
+  partitions.push_back(ImagePartition{
+      .label = "misc",
+      .image_file_path = AbsolutePath(FLAGS_misc_image),
+      .read_only = true,
   });
-  if (!FLAGS_esp.empty()) {
-    partitions.push_back(ImagePartition {
-      .label = "esp",
-      .image_file_path = FLAGS_esp,
-      .type = kEfiSystemPartition,
+  partitions.push_back(ImagePartition{
+      .label = "boot_a",
+      .image_file_path = AbsolutePath(FLAGS_boot_image),
+      .read_only = true,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "boot_b",
+      .image_file_path = AbsolutePath(FLAGS_boot_image),
+      .read_only = true,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "init_boot_a",
+      .image_file_path = AbsolutePath(FLAGS_init_boot_image),
+      .read_only = true,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "init_boot_b",
+      .image_file_path = AbsolutePath(FLAGS_init_boot_image),
+      .read_only = true,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "vendor_boot_a",
+      .image_file_path = AbsolutePath(FLAGS_vendor_boot_image),
+      .read_only = true,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "vendor_boot_b",
+      .image_file_path = AbsolutePath(FLAGS_vendor_boot_image),
+      .read_only = true,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "vbmeta_a",
+      .image_file_path = AbsolutePath(FLAGS_vbmeta_image),
+      .read_only = true,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "vbmeta_b",
+      .image_file_path = AbsolutePath(FLAGS_vbmeta_image),
+      .read_only = true,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "vbmeta_system_a",
+      .image_file_path = AbsolutePath(FLAGS_vbmeta_system_image),
+      .read_only = true,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "vbmeta_system_b",
+      .image_file_path = AbsolutePath(FLAGS_vbmeta_system_image),
+      .read_only = true,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "super",
+      .image_file_path = AbsolutePath(FLAGS_super_image),
+      .read_only = true,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "userdata",
+      .image_file_path = AbsolutePath(FLAGS_data_image),
+      .read_only = true,
+  });
+  partitions.push_back(ImagePartition{
+      .label = "metadata",
+      .image_file_path = AbsolutePath(FLAGS_metadata_image),
+      .read_only = true,
+  });
+  if (!FLAGS_otheros_root_image.empty()) {
+    partitions.push_back(ImagePartition{
+        .label = "otheros_esp",
+        .image_file_path = AbsolutePath(FLAGS_otheros_esp_image),
+        .type = kEfiSystemPartition,
+        .read_only = true,
+    });
+    partitions.push_back(ImagePartition{
+        .label = "otheros_root",
+        .image_file_path = AbsolutePath(FLAGS_otheros_root_image),
+        .read_only = true,
     });
   }
-  partitions.push_back(ImagePartition {
-    .label = "boot_a",
-    .image_file_path = FLAGS_boot_image,
-  });
-  partitions.push_back(ImagePartition {
-    .label = "boot_b",
-    .image_file_path = FLAGS_boot_image,
-  });
-  // Boot image repacking is not supported on protected VMs. Repacking requires
-  // resigning the image and keys on android hosts aren't trusted.
-  if (!FLAGS_protected_vm) {
+  if (!FLAGS_ap_rootfs_image.empty()) {
     partitions.push_back(ImagePartition{
-        .label = "vendor_boot_a",
-        .image_file_path = instance.vendor_boot_image_path(),
-    });
-    partitions.push_back(ImagePartition{
-        .label = "vendor_boot_b",
-        .image_file_path = instance.vendor_boot_image_path(),
-    });
-  } else {
-    partitions.push_back(ImagePartition{
-        .label = "vendor_boot_a",
-        .image_file_path = FLAGS_vendor_boot_image,
-    });
-    partitions.push_back(ImagePartition{
-        .label = "vendor_boot_b",
-        .image_file_path = FLAGS_vendor_boot_image,
+        .label = "ap_rootfs",
+        .image_file_path = AbsolutePath(FLAGS_ap_rootfs_image),
+        .read_only = true,
     });
   }
-  partitions.push_back(ImagePartition {
-    .label = "vbmeta_a",
-    .image_file_path = FLAGS_vbmeta_image,
-  });
-  partitions.push_back(ImagePartition {
-    .label = "vbmeta_b",
-    .image_file_path = FLAGS_vbmeta_image,
-  });
-  partitions.push_back(ImagePartition {
-    .label = "vbmeta_system_a",
-    .image_file_path = FLAGS_vbmeta_system_image,
-  });
-  partitions.push_back(ImagePartition {
-    .label = "vbmeta_system_b",
-    .image_file_path = FLAGS_vbmeta_system_image,
-  });
-  partitions.push_back(ImagePartition {
-    .label = "super",
-    .image_file_path = FLAGS_super_image,
-  });
-  partitions.push_back(ImagePartition {
-    .label = "userdata",
-    .image_file_path = FLAGS_data_image,
-  });
-  partitions.push_back(ImagePartition {
-    .label = "metadata",
-    .image_file_path = FLAGS_metadata_image,
-  });
   return partitions;
 }
 
+DiskBuilder OsCompositeDiskBuilder(const CuttlefishConfig& config) {
+  return DiskBuilder()
+      .Partitions(GetOsCompositeDiskConfig())
+      .VmManager(config.vm_manager())
+      .CrosvmPath(config.crosvm_binary())
+      .ConfigPath(config.AssemblyPath("os_composite_disk_config.txt"))
+      .HeaderPath(config.AssemblyPath("os_composite_gpt_header.img"))
+      .FooterPath(config.AssemblyPath("os_composite_gpt_footer.img"))
+      .CompositeDiskPath(config.os_composite_disk_path())
+      .ResumeIfPossible(FLAGS_resume);
+}
+
 std::vector<ImagePartition> persistent_composite_disk_config(
+    const CuttlefishConfig& config,
     const CuttlefishConfig::InstanceSpecific& instance) {
   std::vector<ImagePartition> partitions;
 
@@ -204,102 +260,28 @@
   // cuttlefish.fragment in external/u-boot).
   partitions.push_back(ImagePartition{
       .label = "uboot_env",
-      .image_file_path = instance.uboot_env_image_path(),
+      .image_file_path = AbsolutePath(instance.uboot_env_image_path()),
+  });
+  partitions.push_back(ImagePartition{
+      .label = "vbmeta",
+      .image_file_path = AbsolutePath(instance.vbmeta_path()),
   });
   if (!FLAGS_protected_vm) {
     partitions.push_back(ImagePartition{
         .label = "frp",
-        .image_file_path = instance.factory_reset_protected_path(),
+        .image_file_path =
+            AbsolutePath(instance.factory_reset_protected_path()),
     });
   }
-  partitions.push_back(ImagePartition{
-      .label = "bootconfig",
-      .image_file_path = instance.persistent_bootconfig_path(),
-  });
+  if (config.bootconfig_supported()) {
+    partitions.push_back(ImagePartition{
+        .label = "bootconfig",
+        .image_file_path = AbsolutePath(instance.persistent_bootconfig_path()),
+    });
+  }
   return partitions;
 }
 
-static std::chrono::system_clock::time_point LastUpdatedInputDisk(
-    const std::vector<ImagePartition>& partitions) {
-  std::chrono::system_clock::time_point ret;
-  for (auto& partition : partitions) {
-    if (partition.label == "frp") {
-      continue;
-    }
-
-    auto partition_mod_time = FileModificationTime(partition.image_file_path);
-    if (partition_mod_time > ret) {
-      ret = partition_mod_time;
-    }
-  }
-  return ret;
-}
-
-bool ShouldCreateAllCompositeDisks(const CuttlefishConfig& config) {
-  std::chrono::system_clock::time_point youngest_disk_img;
-  for (auto& partition :
-       os_composite_disk_config(config.ForDefaultInstance())) {
-    auto partition_mod_time = FileModificationTime(partition.image_file_path);
-    if (partition_mod_time > youngest_disk_img) {
-      youngest_disk_img = partition_mod_time;
-    }
-  }
-
-  // If the youngest partition img is younger than any composite disk, this fact implies that
-  // the composite disks are all out of date and need to be reinitialized.
-  for (auto& instance : config.Instances()) {
-    if (!FileExists(instance.os_composite_disk_path())) {
-      continue;
-    }
-    if (youngest_disk_img >
-        FileModificationTime(instance.os_composite_disk_path())) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
-bool DoesCompositeMatchCurrentDiskConfig(
-    const std::string& prior_disk_config_path,
-    const std::vector<ImagePartition>& partitions) {
-  std::string current_disk_config_path = prior_disk_config_path + ".tmp";
-  std::ostringstream disk_conf;
-  for (auto& partition : partitions) {
-    disk_conf << partition.image_file_path << "\n";
-  }
-
-  {
-    // This file acts as a descriptor of the cuttlefish disk contents in a VMM agnostic way (VMMs
-    // used are QEMU and CrosVM at the time of writing). This file is used to determine if the
-    // disk config for the pending boot matches the disk from the past boot.
-    std::ofstream file_out(current_disk_config_path.c_str(), std::ios::binary);
-    file_out << disk_conf.str();
-    CHECK(file_out.good()) << "Disk config verification failed.";
-  }
-
-  if (!FileExists(prior_disk_config_path) ||
-      ReadFile(prior_disk_config_path) != ReadFile(current_disk_config_path)) {
-    CHECK(cuttlefish::RenameFile(current_disk_config_path, prior_disk_config_path))
-        << "Unable to delete the old disk config descriptor";
-    LOG(DEBUG) << "Disk Config has changed since last boot. Regenerating composite disk.";
-    return false;
-  } else {
-    RemoveFile(current_disk_config_path);
-    return true;
-  }
-}
-
-bool ShouldCreateCompositeDisk(const std::string& composite_disk_path,
-                               const std::vector<ImagePartition>& partitions) {
-  if (!FileExists(composite_disk_path)) {
-    return true;
-  }
-
-  auto composite_age = FileModificationTime(composite_disk_path);
-  return composite_age < LastUpdatedInputDisk(partitions);
-}
-
 static uint64_t AvailableSpaceAtPath(const std::string& path) {
   struct statvfs vfs;
   if (statvfs(path.c_str(), &vfs) != 0) {
@@ -312,285 +294,757 @@
   return static_cast<uint64_t>(vfs.f_frsize) * vfs.f_bavail;
 }
 
-bool CreateCompositeDisk(const CuttlefishConfig& config,
-                         const CuttlefishConfig::InstanceSpecific& instance) {
-  if (!SharedFD::Open(instance.os_composite_disk_path().c_str(),
-                      O_WRONLY | O_CREAT, 0644)
-           ->IsOpen()) {
-    LOG(ERROR) << "Could not ensure " << instance.os_composite_disk_path()
-               << " exists";
-    return false;
+class BootImageRepacker : public SetupFeature {
+ public:
+  INJECT(BootImageRepacker(const CuttlefishConfig& config)) : config_(config) {}
+
+  // SetupFeature
+  std::string Name() const override { return "BootImageRepacker"; }
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Enabled() const override {
+    // If we are booting a protected VM, for now, assume that image repacking
+    // isn't trusted. Repacking requires resigning the image and keys from an
+    // android host aren't trusted.
+    return !config_.protected_vm();
   }
-  if (config.vm_manager() == CrosvmManager::name()) {
-    // Check if filling in the sparse image would run out of disk space.
-    auto existing_sizes = SparseFileSizes(FLAGS_data_image);
-    if (existing_sizes.sparse_size == 0 && existing_sizes.disk_size == 0) {
-      LOG(ERROR) << "Unable to determine size of \"" << FLAGS_data_image
-                 << "\". Does this file exist?";
-    }
-    auto available_space = AvailableSpaceAtPath(FLAGS_data_image);
-    if (available_space < existing_sizes.sparse_size - existing_sizes.disk_size) {
-      // TODO(schuffelen): Duplicate this check in run_cvd when it can run on a separate machine
-      LOG(ERROR) << "Not enough space remaining in fs containing " << FLAGS_data_image;
-      LOG(ERROR) << "Wanted " << (existing_sizes.sparse_size - existing_sizes.disk_size);
-      LOG(ERROR) << "Got " << available_space;
+
+ protected:
+  bool Setup() override {
+    if (!FileHasContent(FLAGS_boot_image)) {
+      LOG(ERROR) << "File not found: " << FLAGS_boot_image;
       return false;
-    } else {
-      LOG(DEBUG) << "Available space: " << available_space;
-      LOG(DEBUG) << "Sparse size of \"" << FLAGS_data_image << "\": "
-                 << existing_sizes.sparse_size;
-      LOG(DEBUG) << "Disk size of \"" << FLAGS_data_image << "\": "
-                 << existing_sizes.disk_size;
     }
-    std::string header_path =
-        instance.PerInstancePath("os_composite_gpt_header.img");
-    std::string footer_path =
-        instance.PerInstancePath("os_composite_gpt_footer.img");
-    CreateCompositeDisk(os_composite_disk_config(instance), header_path,
-                        footer_path, instance.os_composite_disk_path());
-  } else {
-    // If this doesn't fit into the disk, it will fail while aggregating. The
-    // aggregator doesn't maintain any sparse attributes.
-    AggregateImage(os_composite_disk_config(instance),
-                   instance.os_composite_disk_path());
-  }
-  return true;
-}
+    // The init_boot partition is be optional for testing boot.img
+    // with the ramdisk inside.
+    if (!FileHasContent(FLAGS_init_boot_image)) {
+      LOG(WARNING) << "File not found: " << FLAGS_init_boot_image;
+    }
 
-bool CreatePersistentCompositeDisk(
-    const CuttlefishConfig& config,
-    const CuttlefishConfig::InstanceSpecific& instance) {
-  if (!SharedFD::Open(instance.persistent_composite_disk_path().c_str(),
-                      O_WRONLY | O_CREAT, 0644)
-           ->IsOpen()) {
-    LOG(ERROR) << "Could not ensure "
-               << instance.persistent_composite_disk_path() << " exists";
-    return false;
-  }
-  if (config.vm_manager() == CrosvmManager::name()) {
-    std::string header_path =
-        instance.PerInstancePath("persistent_composite_gpt_header.img");
-    std::string footer_path =
-        instance.PerInstancePath("persistent_composite_gpt_footer.img");
-    CreateCompositeDisk(persistent_composite_disk_config(instance), header_path,
-                        footer_path, instance.persistent_composite_disk_path());
-  } else {
-    AggregateImage(persistent_composite_disk_config(instance),
-                   instance.persistent_composite_disk_path());
-  }
-  return true;
-}
+    if (!FileHasContent(FLAGS_vendor_boot_image)) {
+      LOG(ERROR) << "File not found: " << FLAGS_vendor_boot_image;
+      return false;
+    }
 
-static void RepackAllBootImages(const CuttlefishConfig* config) {
-  CHECK(FileHasContent(FLAGS_boot_image))
-      << "File not found: " << FLAGS_boot_image;
+    // Repacking a boot.img doesn't work with Gem5 because the user must always
+    // specify a vmlinux instead of an arm64 Image, and that file can be too
+    // large to be repacked. Skip repack of boot.img on Gem5, as we need to be
+    // able to extract the ramdisk.img in a later stage and so this step must
+    // not fail (..and the repacked kernel wouldn't be used anyway).
+    if (FLAGS_kernel_path.size() &&
+        config_.vm_manager() != Gem5Manager::name()) {
+      const std::string new_boot_image_path =
+          config_.AssemblyPath("boot_repacked.img");
+      bool success =
+          RepackBootImage(FLAGS_kernel_path, FLAGS_boot_image,
+                          new_boot_image_path, config_.assembly_dir());
+      if (!success) {
+        LOG(ERROR) << "Failed to regenerate the boot image with the new kernel";
+        return false;
+      }
+      SetCommandLineOptionWithMode("boot_image", new_boot_image_path.c_str(),
+                                   google::FlagSettingMode::SET_FLAGS_DEFAULT);
+    }
 
-  CHECK(FileHasContent(FLAGS_vendor_boot_image))
-      << "File not found: " << FLAGS_vendor_boot_image;
-
-  if (FLAGS_kernel_path.size()) {
-    const std::string new_boot_image_path =
-        config->AssemblyPath("boot_repacked.img");
-    bool success = RepackBootImage(FLAGS_kernel_path, FLAGS_boot_image,
-                                   new_boot_image_path, config->assembly_dir());
-    CHECK(success) << "Failed to regenerate the boot image with the new kernel";
-    SetCommandLineOptionWithMode("boot_image", new_boot_image_path.c_str(),
-                                 google::FlagSettingMode::SET_FLAGS_DEFAULT);
-  }
-
-  for (auto instance : config->Instances()) {
-    const std::string new_vendor_boot_image_path =
-        instance.vendor_boot_image_path();
-    const std::vector<std::string> boot_config_vector =
-        BootconfigArgsFromConfig(*config, instance);
     if (FLAGS_kernel_path.size() || FLAGS_initramfs_path.size()) {
+      const std::string new_vendor_boot_image_path =
+          config_.AssemblyPath("vendor_boot_repacked.img");
       // Repack the vendor boot images if kernels and/or ramdisks are passed in.
       if (FLAGS_initramfs_path.size()) {
         bool success = RepackVendorBootImage(
             FLAGS_initramfs_path, FLAGS_vendor_boot_image,
-            new_vendor_boot_image_path, config->assembly_dir(),
-            instance.instance_dir(), boot_config_vector,
-            config->bootconfig_supported());
-        CHECK(success) << "Failed to regenerate the vendor boot image with the "
-                          "new ramdisk";
-      } else {
-        // This control flow implies a kernel with all configs built in.
-        // If it's just the kernel, repack the vendor boot image without a
-        // ramdisk.
-        bool success = RepackVendorBootImageWithEmptyRamdisk(
-            FLAGS_vendor_boot_image, new_vendor_boot_image_path,
-            config->assembly_dir(), instance.instance_dir(), boot_config_vector,
-            config->bootconfig_supported());
-        CHECK(success)
-            << "Failed to regenerate the vendor boot image without a ramdisk";
+            new_vendor_boot_image_path, config_.assembly_dir(),
+            config_.bootconfig_supported());
+        if (!success) {
+          LOG(ERROR) << "Failed to regenerate the vendor boot image with the "
+                        "new ramdisk";
+        } else {
+          // This control flow implies a kernel with all configs built in.
+          // If it's just the kernel, repack the vendor boot image without a
+          // ramdisk.
+          bool success = RepackVendorBootImageWithEmptyRamdisk(
+              FLAGS_vendor_boot_image, new_vendor_boot_image_path,
+              config_.assembly_dir(), config_.bootconfig_supported());
+          if (!success) {
+            LOG(ERROR) << "Failed to regenerate the vendor boot image without "
+                          "a ramdisk";
+            return false;
+          }
+        }
+        SetCommandLineOptionWithMode(
+            "vendor_boot_image", new_vendor_boot_image_path.c_str(),
+            google::FlagSettingMode::SET_FLAGS_DEFAULT);
+      }
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig& config_;
+};
+
+class Gem5ImageUnpacker : public SetupFeature {
+ public:
+  INJECT(Gem5ImageUnpacker(
+      const CuttlefishConfig& config,
+      BootImageRepacker& bir))
+      : config_(config),
+        bir_(bir) {}
+
+  // SetupFeature
+  std::string Name() const override { return "Gem5ImageUnpacker"; }
+
+  std::unordered_set<SetupFeature*> Dependencies() const override {
+    return {
+        static_cast<SetupFeature*>(&bir_),
+    };
+  }
+
+  bool Enabled() const override {
+    // Everything has a bootloader except gem5, so only run this for gem5
+    return config_.vm_manager() == Gem5Manager::name();
+  }
+
+ protected:
+  bool Setup() override {
+    /* Unpack the original or repacked boot and vendor boot ramdisks, so that
+     * we have access to the baked bootconfig and raw compressed ramdisks.
+     * This allows us to emulate what a bootloader would normally do, which
+     * Gem5 can't support itself. This code also copies the kernel again
+     * (because Gem5 only supports raw vmlinux) and handles the bootloader
+     * binaries specially. This code is just part of the solution; it only
+     * does the parts which are instance agnostic.
+     */
+
+    if (!FileHasContent(FLAGS_boot_image)) {
+      LOG(ERROR) << "File not found: " << FLAGS_boot_image;
+      return false;
+    }
+    // The init_boot partition is be optional for testing boot.img
+    // with the ramdisk inside.
+    if (!FileHasContent(FLAGS_init_boot_image)) {
+      LOG(WARNING) << "File not found: " << FLAGS_init_boot_image;
+    }
+
+    if (!FileHasContent(FLAGS_vendor_boot_image)) {
+      LOG(ERROR) << "File not found: " << FLAGS_vendor_boot_image;
+      return false;
+    }
+
+    const std::string unpack_dir = config_.assembly_dir();
+
+    bool success = UnpackBootImage(FLAGS_init_boot_image, unpack_dir);
+    if (!success) {
+      LOG(ERROR) << "Failed to extract the init boot image";
+      return false;
+    }
+
+    success = UnpackVendorBootImageIfNotUnpacked(FLAGS_vendor_boot_image,
+                                                 unpack_dir);
+    if (!success) {
+      LOG(ERROR) << "Failed to extract the vendor boot image";
+      return false;
+    }
+
+    // Assume the user specified a kernel manually which is a vmlinux
+    std::ofstream kernel(unpack_dir + "/kernel", std::ios_base::binary |
+                                                 std::ios_base::trunc);
+    std::ifstream vmlinux(FLAGS_kernel_path, std::ios_base::binary);
+    kernel << vmlinux.rdbuf();
+    kernel.close();
+
+    // Gem5 needs the bootloader binary to be a specific directory structure
+    // to find it. Create a 'binaries' directory and copy it into there
+    const std::string binaries_dir = unpack_dir + "/binaries";
+    if (mkdir(binaries_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
+        && errno != EEXIST) {
+      PLOG(ERROR) << "Failed to create dir: \"" << binaries_dir << "\" ";
+      return false;
+    }
+    std::ofstream bootloader(binaries_dir + "/" +
+                             cpp_basename(FLAGS_bootloader),
+                             std::ios_base::binary | std::ios_base::trunc);
+    std::ifstream src_bootloader(FLAGS_bootloader, std::ios_base::binary);
+    bootloader << src_bootloader.rdbuf();
+    bootloader.close();
+
+    // Gem5 also needs the ARM version of the bootloader, even though it
+    // doesn't use it. It'll even open it to check it's a valid ELF file.
+    // Work around this by copying such a named file from the same directory
+    std::ofstream boot_arm(binaries_dir + "/boot.arm",
+                           std::ios_base::binary | std::ios_base::trunc);
+    std::ifstream src_boot_arm(cpp_dirname(FLAGS_bootloader) + "/boot.arm",
+                               std::ios_base::binary);
+    boot_arm << src_boot_arm.rdbuf();
+    boot_arm.close();
+
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig& config_;
+  BootImageRepacker& bir_;
+};
+
+class GeneratePersistentBootconfig : public SetupFeature {
+ public:
+  INJECT(GeneratePersistentBootconfig(
+      const CuttlefishConfig& config,
+      const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // SetupFeature
+  std::string Name() const override {
+    return "GeneratePersistentBootconfig";
+  }
+  bool Enabled() const override {
+    return (!config_.protected_vm());
+  }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override {
+    //  Cuttlefish for the time being won't be able to support OTA from a
+    //  non-bootconfig kernel to a bootconfig-kernel (or vice versa) IF the
+    //  device is stopped (via stop_cvd). This is rarely an issue since OTA
+    //  testing run on cuttlefish is done within one launch cycle of the device.
+    //  If this ever becomes an issue, this code will have to be rewritten.
+    if(!config_.bootconfig_supported()) {
+      return true;
+    }
+
+    const auto bootconfig_path = instance_.persistent_bootconfig_path();
+    if (!FileExists(bootconfig_path)) {
+      if (!CreateBlankImage(bootconfig_path, 1 /* mb */, "none")) {
+        LOG(ERROR) << "Failed to create image at " << bootconfig_path;
+        return false;
+      }
+    }
+
+    auto bootconfig_fd = SharedFD::Open(bootconfig_path, O_RDWR);
+    if (!bootconfig_fd->IsOpen()) {
+      LOG(ERROR) << "Unable to open bootconfig file: "
+                 << bootconfig_fd->StrError();
+      return false;
+    }
+
+    const std::string bootconfig =
+        android::base::Join(BootconfigArgsFromConfig(config_, instance_),
+                            "\n") +
+        "\n";
+    ssize_t bytesWritten = WriteAll(bootconfig_fd, bootconfig);
+    LOG(DEBUG) << "bootconfig size is " << bytesWritten;
+    if (bytesWritten != bootconfig.size()) {
+      LOG(ERROR) << "Failed to write contents of bootconfig to \""
+                 << bootconfig_path << "\"";
+      return false;
+    }
+    LOG(DEBUG) << "Bootconfig parameters from vendor boot image and config are "
+               << ReadFile(bootconfig_path);
+
+    if (bootconfig_fd->Truncate(bytesWritten) != 0) {
+      LOG(ERROR) << "`truncate --size=" << bytesWritten << " bytes "
+                 << bootconfig_path << "` failed:" << bootconfig_fd->StrError();
+      return false;
+    }
+
+    if (config_.vm_manager() != Gem5Manager::name()) {
+      bootconfig_fd->Close();
+      const off_t bootconfig_size_bytes = AlignToPowerOf2(
+          MAX_AVB_METADATA_SIZE + bytesWritten, PARTITION_SIZE_SHIFT);
+
+      auto avbtool_path = HostBinaryPath("avbtool");
+      Command bootconfig_hash_footer_cmd(avbtool_path);
+      bootconfig_hash_footer_cmd.AddParameter("add_hash_footer");
+      bootconfig_hash_footer_cmd.AddParameter("--image");
+      bootconfig_hash_footer_cmd.AddParameter(bootconfig_path);
+      bootconfig_hash_footer_cmd.AddParameter("--partition_size");
+      bootconfig_hash_footer_cmd.AddParameter(bootconfig_size_bytes);
+      bootconfig_hash_footer_cmd.AddParameter("--partition_name");
+      bootconfig_hash_footer_cmd.AddParameter("bootconfig");
+      bootconfig_hash_footer_cmd.AddParameter("--key");
+      bootconfig_hash_footer_cmd.AddParameter(
+          DefaultHostArtifactsPath("etc/cvd_avb_testkey.pem"));
+      bootconfig_hash_footer_cmd.AddParameter("--algorithm");
+      bootconfig_hash_footer_cmd.AddParameter("SHA256_RSA4096");
+      int success = bootconfig_hash_footer_cmd.Start().Wait();
+      if (success != 0) {
+        LOG(ERROR) << "Unable to run append hash footer. Exited with status "
+                   << success;
+        return false;
       }
     } else {
-      // Repack the vendor boot image to add the instance specific bootconfig
-      // parameters
-      bool success = RepackVendorBootImage(
-          std::string(), FLAGS_vendor_boot_image, new_vendor_boot_image_path,
-          config->assembly_dir(), instance.instance_dir(), boot_config_vector,
-          config->bootconfig_supported());
-      CHECK(success) << "Failed to regenerate the vendor boot image";
+      const off_t bootconfig_size_bytes_gem5 = AlignToPowerOf2(
+          bytesWritten, PARTITION_SIZE_SHIFT);
+      bootconfig_fd->Truncate(bootconfig_size_bytes_gem5);
+      bootconfig_fd->Close();
     }
+    return true;
   }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class GeneratePersistentVbmeta : public SetupFeature {
+ public:
+  INJECT(GeneratePersistentVbmeta(
+      const CuttlefishConfig& config,
+      const CuttlefishConfig::InstanceSpecific& instance,
+      InitBootloaderEnvPartition& bootloader_env,
+      GeneratePersistentBootconfig& bootconfig))
+      : config_(config),
+        instance_(instance),
+        bootloader_env_(bootloader_env),
+        bootconfig_(bootconfig) {}
+
+  // SetupFeature
+  std::string Name() const override {
+    return "GeneratePersistentVbmeta";
+  }
+  bool Enabled() const override {
+    return (!config_.protected_vm());
+  }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override {
+    return {
+        static_cast<SetupFeature*>(&bootloader_env_),
+        static_cast<SetupFeature*>(&bootconfig_),
+    };
+  }
+
+  bool Setup() override {
+    auto avbtool_path = HostBinaryPath("avbtool");
+    Command vbmeta_cmd(avbtool_path);
+    vbmeta_cmd.AddParameter("make_vbmeta_image");
+    vbmeta_cmd.AddParameter("--output");
+    vbmeta_cmd.AddParameter(instance_.vbmeta_path());
+    vbmeta_cmd.AddParameter("--algorithm");
+    vbmeta_cmd.AddParameter("SHA256_RSA4096");
+    vbmeta_cmd.AddParameter("--key");
+    vbmeta_cmd.AddParameter(
+        DefaultHostArtifactsPath("etc/cvd_avb_testkey.pem"));
+
+    vbmeta_cmd.AddParameter("--chain_partition");
+    vbmeta_cmd.AddParameter("uboot_env:1:" +
+                            DefaultHostArtifactsPath("etc/cvd.avbpubkey"));
+
+    if (config_.bootconfig_supported()) {
+        vbmeta_cmd.AddParameter("--chain_partition");
+        vbmeta_cmd.AddParameter("bootconfig:2:" +
+                                DefaultHostArtifactsPath("etc/cvd.avbpubkey"));
+    }
+
+    bool success = vbmeta_cmd.Start().Wait();
+    if (success != 0) {
+      LOG(ERROR) << "Unable to create persistent vbmeta. Exited with status "
+                 << success;
+      return false;
+    }
+
+    if (FileSize(instance_.vbmeta_path()) > VBMETA_MAX_SIZE) {
+      LOG(ERROR) << "Generated vbmeta - " << instance_.vbmeta_path()
+                 << " is larger than the expected " << VBMETA_MAX_SIZE
+                 << ". Stopping.";
+      return false;
+    }
+    if (FileSize(instance_.vbmeta_path()) != VBMETA_MAX_SIZE) {
+      auto fd = SharedFD::Open(instance_.vbmeta_path(), O_RDWR);
+      if (!fd->IsOpen() || fd->Truncate(VBMETA_MAX_SIZE) != 0) {
+        LOG(ERROR) << "`truncate --size=" << VBMETA_MAX_SIZE << " "
+                   << instance_.vbmeta_path() << "` "
+                   << "failed: " << fd->StrError();
+        return false;
+      }
+    }
+    return true;
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  InitBootloaderEnvPartition& bootloader_env_;
+  GeneratePersistentBootconfig& bootconfig_;
+};
+
+class InitializeMetadataImage : public SetupFeature {
+ public:
+  INJECT(InitializeMetadataImage()) {}
+
+  // SetupFeature
+  std::string Name() const override { return "InitializeMetadataImage"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    if (FileExists(FLAGS_metadata_image)) {
+      return {};
+    }
+
+    CF_EXPECT(CreateBlankImage(FLAGS_metadata_image,
+                               FLAGS_blank_metadata_image_mb, "none"),
+              "Failed to create \"" << FLAGS_metadata_image << "\" with size "
+                                    << FLAGS_blank_metadata_image_mb);
+    return {};
+  }
+};
+
+class InitializeAccessKregistryImage : public SetupFeature {
+ public:
+  INJECT(InitializeAccessKregistryImage(
+      const CuttlefishConfig& config,
+      const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // SetupFeature
+  std::string Name() const override { return "InitializeAccessKregistryImage"; }
+  bool Enabled() const override { return !config_.protected_vm(); }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    auto access_kregistry = instance_.access_kregistry_path();
+    if (FileExists(access_kregistry)) {
+      return {};
+    }
+    CF_EXPECT(CreateBlankImage(access_kregistry, 2 /* mb */, "none"),
+              "Failed to create \"" << access_kregistry << "\"");
+    return {};
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class InitializeHwcomposerPmemImage : public SetupFeature {
+ public:
+  INJECT(InitializeHwcomposerPmemImage(
+      const CuttlefishConfig& config,
+      const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // SetupFeature
+  std::string Name() const override { return "InitializeHwcomposerPmemImage"; }
+  bool Enabled() const override { return !config_.protected_vm(); }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    if (FileExists(instance_.hwcomposer_pmem_path())) {
+      return {};
+    }
+    CF_EXPECT(
+        CreateBlankImage(instance_.hwcomposer_pmem_path(), 2 /* mb */, "none"),
+        "Failed creating \"" << instance_.hwcomposer_pmem_path() << "\"");
+    return {};
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class InitializePstore : public SetupFeature {
+ public:
+  INJECT(InitializePstore(const CuttlefishConfig& config,
+                          const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // SetupFeature
+  std::string Name() const override { return "InitializePstore"; }
+  bool Enabled() const override { return !config_.protected_vm(); }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    if (FileExists(instance_.pstore_path())) {
+      return {};
+    }
+
+    CF_EXPECT(CreateBlankImage(instance_.pstore_path(), 2 /* mb */, "none"),
+              "Failed to create \"" << instance_.pstore_path() << "\"");
+    return {};
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class InitializeSdCard : public SetupFeature {
+ public:
+  INJECT(InitializeSdCard(const CuttlefishConfig& config,
+                          const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // SetupFeature
+  std::string Name() const override { return "InitializeSdCard"; }
+  bool Enabled() const override {
+    return FLAGS_use_sdcard && !config_.protected_vm();
+  }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    if (FileExists(instance_.sdcard_path())) {
+      return {};
+    }
+    CF_EXPECT(CreateBlankImage(instance_.sdcard_path(),
+                               FLAGS_blank_sdcard_image_mb, "sdcard"),
+              "Failed to create \"" << instance_.sdcard_path() << "\"");
+    return {};
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class InitializeFactoryResetProtected : public SetupFeature {
+ public:
+  INJECT(InitializeFactoryResetProtected(
+      const CuttlefishConfig& config,
+      const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // SetupFeature
+  std::string Name() const override { return "InitializeSdCard"; }
+  bool Enabled() const override { return !config_.protected_vm(); }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    auto frp = instance_.factory_reset_protected_path();
+    if (FileExists(frp)) {
+      return {};
+    }
+    CF_EXPECT(CreateBlankImage(frp, 1 /* mb */, "none"),
+              "Failed to create \"" << frp << "\"");
+    return {};
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class InitializeInstanceCompositeDisk : public SetupFeature {
+ public:
+  INJECT(InitializeInstanceCompositeDisk(
+      const CuttlefishConfig& config,
+      const CuttlefishConfig::InstanceSpecific& instance,
+      InitializeFactoryResetProtected& frp,
+      GeneratePersistentVbmeta& vbmeta))
+      : config_(config),
+        instance_(instance),
+        frp_(frp),
+        vbmeta_(vbmeta) {}
+
+  std::string Name() const override {
+    return "InitializeInstanceCompositeDisk";
+  }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override {
+    return {
+        static_cast<SetupFeature*>(&frp_),
+        static_cast<SetupFeature*>(&vbmeta_),
+    };
+  }
+  Result<void> ResultSetup() override {
+    auto ipath = [this](const std::string& path) -> std::string {
+      return instance_.PerInstancePath(path.c_str());
+    };
+    auto persistent_disk_builder =
+        DiskBuilder()
+            .Partitions(persistent_composite_disk_config(config_, instance_))
+            .VmManager(config_.vm_manager())
+            .CrosvmPath(config_.crosvm_binary())
+            .ConfigPath(ipath("persistent_composite_disk_config.txt"))
+            .HeaderPath(ipath("persistent_composite_gpt_header.img"))
+            .FooterPath(ipath("persistent_composite_gpt_footer.img"))
+            .CompositeDiskPath(instance_.persistent_composite_disk_path())
+            .ResumeIfPossible(FLAGS_resume);
+
+    CF_EXPECT(persistent_disk_builder.BuildCompositeDiskIfNecessary());
+    return {};
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  InitializeFactoryResetProtected& frp_;
+  GeneratePersistentVbmeta& vbmeta_;
+};
+
+class VbmetaEnforceMinimumSize : public SetupFeature {
+ public:
+  INJECT(VbmetaEnforceMinimumSize()) {}
+
+  std::string Name() const override { return "VbmetaEnforceMinimumSize"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    // libavb expects to be able to read the maximum vbmeta size, so we must
+    // provide a partition which matches this or the read will fail
+    for (const auto& vbmeta_image :
+         {FLAGS_vbmeta_image, FLAGS_vbmeta_system_image}) {
+      if (FileSize(vbmeta_image) != VBMETA_MAX_SIZE) {
+        auto fd = SharedFD::Open(vbmeta_image, O_RDWR);
+        CF_EXPECT(fd->IsOpen(), "Could not open \"" << vbmeta_image << "\": "
+                                                    << fd->StrError());
+        CF_EXPECT(fd->Truncate(VBMETA_MAX_SIZE) == 0,
+                  "`truncate --size=" << VBMETA_MAX_SIZE << " " << vbmeta_image
+                                      << "` failed: " << fd->StrError());
+      }
+    }
+    return {};
+  }
+};
+
+class BootloaderPresentCheck : public SetupFeature {
+ public:
+  INJECT(BootloaderPresentCheck()) {}
+
+  std::string Name() const override { return "BootloaderPresentCheck"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    CF_EXPECT(FileHasContent(FLAGS_bootloader),
+              "File not found: " << FLAGS_bootloader);
+    return {};
+  }
+};
+
+static fruit::Component<> DiskChangesComponent(const FetcherConfig* fetcher,
+                                               const CuttlefishConfig* config) {
+  return fruit::createComponent()
+      .bindInstance(*fetcher)
+      .bindInstance(*config)
+      .addMultibinding<SetupFeature, InitializeMetadataImage>()
+      .addMultibinding<SetupFeature, BootImageRepacker>()
+      .addMultibinding<SetupFeature, VbmetaEnforceMinimumSize>()
+      .addMultibinding<SetupFeature, BootloaderPresentCheck>()
+      .addMultibinding<SetupFeature, Gem5ImageUnpacker>()
+      .install(FixedMiscImagePathComponent, &FLAGS_misc_image)
+      .install(InitializeMiscImageComponent)
+      .install(FixedDataImagePathComponent, &FLAGS_data_image)
+      .install(InitializeDataImageComponent)
+      // Create esp if necessary
+      .install(InitializeEspImageComponent, &FLAGS_otheros_esp_image,
+               &FLAGS_otheros_kernel_path, &FLAGS_otheros_initramfs_path,
+               &FLAGS_otheros_root_image, config)
+      .install(SuperImageRebuilderComponent, &FLAGS_super_image);
 }
 
-void CreateDynamicDiskFiles(const FetcherConfig& fetcher_config,
-                            const CuttlefishConfig* config) {
-  // Create misc if necessary
-  CHECK(InitializeMiscImage(FLAGS_misc_image)) << "Failed to create misc image";
+static fruit::Component<> DiskChangesPerInstanceComponent(
+    const FetcherConfig* fetcher, const CuttlefishConfig* config,
+    const CuttlefishConfig::InstanceSpecific* instance) {
+  return fruit::createComponent()
+      .bindInstance(*fetcher)
+      .bindInstance(*config)
+      .bindInstance(*instance)
+      .addMultibinding<SetupFeature, InitializeAccessKregistryImage>()
+      .addMultibinding<SetupFeature, InitializeHwcomposerPmemImage>()
+      .addMultibinding<SetupFeature, InitializePstore>()
+      .addMultibinding<SetupFeature, InitializeSdCard>()
+      .addMultibinding<SetupFeature, InitializeFactoryResetProtected>()
+      .addMultibinding<SetupFeature, GeneratePersistentBootconfig>()
+      .addMultibinding<SetupFeature, GeneratePersistentVbmeta>()
+      .addMultibinding<SetupFeature, InitializeInstanceCompositeDisk>()
+      .install(InitBootloaderEnvPartitionComponent);
+}
 
-  // Create data if necessary
-  DataImageResult dataImageResult = ApplyDataImagePolicy(*config, FLAGS_data_image);
-  CHECK(dataImageResult != DataImageResult::Error) << "Failed to set up userdata";
+Result<void> CreateDynamicDiskFiles(const FetcherConfig& fetcher_config,
+                                    const CuttlefishConfig& config) {
+  // TODO(schuffelen): Unify this with the other injector created in
+  // assemble_cvd.cpp
+  fruit::Injector<> injector(DiskChangesComponent, &fetcher_config, &config);
 
-  if (!FileExists(FLAGS_metadata_image)) {
-    CreateBlankImage(FLAGS_metadata_image, FLAGS_blank_metadata_image_mb, "none");
+  const auto& features = injector.getMultibindings<SetupFeature>();
+  CF_EXPECT(SetupFeature::RunSetup(features));
+
+  for (const auto& instance : config.Instances()) {
+    fruit::Injector<> instance_injector(DiskChangesPerInstanceComponent,
+                                        &fetcher_config, &config, &instance);
+    const auto& instance_features =
+        instance_injector.getMultibindings<SetupFeature>();
+    CF_EXPECT(SetupFeature::RunSetup(instance_features),
+              "instance = \"" << instance.instance_name() << "\"");
   }
 
-  // If we are booting a protected VM, for now, assume we want a super minimal
-  // environment with no userdata encryption, limited debug, no FRP emulation, a
-  // static env for the bootloader, no SD-Card and no resume-on-reboot HAL
-  // support. We can also assume that image repacking isn't trusted. Repacking
-  // requires resigning the image and keys from an android host aren't trusted.
-  if (!FLAGS_protected_vm) {
-    RepackAllBootImages(config);
-
-    for (const auto& instance : config->Instances()) {
-      if (!FileExists(instance.access_kregistry_path())) {
-        CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none");
-      }
-
-      if (!FileExists(instance.pstore_path())) {
-        CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none");
-      }
-
-      if (FLAGS_use_sdcard && !FileExists(instance.sdcard_path())) {
-        CreateBlankImage(instance.sdcard_path(),
-                         FLAGS_blank_sdcard_image_mb, "sdcard");
-      }
-
-      CHECK(InitBootloaderEnvPartition(*config, instance))
-          << "Failed to create bootloader environment partition";
-
-      const auto frp = instance.factory_reset_protected_path();
-      if (!FileExists(frp)) {
-        CreateBlankImage(frp, 1 /* mb */, "none");
-      }
-
-      const auto bootconfig_path = instance.persistent_bootconfig_path();
-      if (!FileExists(bootconfig_path)) {
-        CreateBlankImage(bootconfig_path, 1 /* mb */, "none");
-      }
-
-      auto bootconfig_fd = SharedFD::Open(bootconfig_path, O_RDWR);
-      CHECK(bootconfig_fd->IsOpen())
-          << "Unable to open bootconfig file: " << bootconfig_fd->StrError();
-
-      const std::string bootconfig =
-          android::base::Join(BootconfigArgsFromConfig(*config, instance),
-                              "\n") +
-          "\n";
-      ssize_t bytesWritten = WriteAll(bootconfig_fd, bootconfig);
-      CHECK(bytesWritten == bootconfig.size());
-      LOG(DEBUG)
-          << "Bootconfig parameters from vendor boot image and config are "
-          << ReadFile(bootconfig_path);
-
-      const off_t bootconfig_size_bytes =
-          AlignToPowerOf2(bootconfig.size(), PARTITION_SIZE_SHIFT);
-      CHECK(bootconfig_fd->Truncate(bootconfig_size_bytes) == 0)
-          << "`truncate --size=" << bootconfig_size_bytes << " bytes "
-          << bootconfig_path << "` failed:" << bootconfig_fd->StrError();
-    }
+  // Check if filling in the sparse image would run out of disk space.
+  auto existing_sizes = SparseFileSizes(FLAGS_data_image);
+  CF_EXPECT(existing_sizes.sparse_size > 0 || existing_sizes.disk_size > 0,
+            "Unable to determine size of \"" << FLAGS_data_image
+                                             << "\". Does this file exist?");
+  auto available_space = AvailableSpaceAtPath(FLAGS_data_image);
+  if (available_space < existing_sizes.sparse_size - existing_sizes.disk_size) {
+    // TODO(schuffelen): Duplicate this check in run_cvd when it can run on a
+    // separate machine
+    return CF_ERR("Not enough space remaining in fs containing \""
+                  << FLAGS_data_image << "\", wanted "
+                  << (existing_sizes.sparse_size - existing_sizes.disk_size)
+                  << ", got " << available_space);
+  } else {
+    LOG(DEBUG) << "Available space: " << available_space;
+    LOG(DEBUG) << "Sparse size of \"" << FLAGS_data_image
+               << "\": " << existing_sizes.sparse_size;
+    LOG(DEBUG) << "Disk size of \"" << FLAGS_data_image
+               << "\": " << existing_sizes.disk_size;
   }
 
-  for (const auto& instance : config->Instances()) {
-    bool compositeMatchesDiskConfig = DoesCompositeMatchCurrentDiskConfig(
-        instance.PerInstancePath("persistent_composite_disk_config.txt"),
-        persistent_composite_disk_config(instance));
-    bool oldCompositeDisk =
-        ShouldCreateCompositeDisk(instance.persistent_composite_disk_path(),
-                                  persistent_composite_disk_config(instance));
-
-    if (!compositeMatchesDiskConfig || oldCompositeDisk) {
-      CHECK(CreatePersistentCompositeDisk(*config, instance))
-          << "Failed to create persistent composite disk";
-    }
-  }
-
-  // libavb expects to be able to read the maximum vbmeta size, so we must
-  // provide a partition which matches this or the read will fail
-  for (const auto& vbmeta_image : { FLAGS_vbmeta_image, FLAGS_vbmeta_system_image }) {
-    if (FileSize(vbmeta_image) != VBMETA_MAX_SIZE) {
-      auto fd = SharedFD::Open(vbmeta_image, O_RDWR);
-      CHECK(fd->Truncate(VBMETA_MAX_SIZE) == 0)
-        << "`truncate --size=" << VBMETA_MAX_SIZE << " " << vbmeta_image << "` "
-        << "failed: " << fd->StrError();
-    }
-  }
-
-  CHECK(FileHasContent(FLAGS_bootloader))
-      << "File not found: " << FLAGS_bootloader;
-
-  if (!FLAGS_esp.empty()) {
-    CHECK(FileHasContent(FLAGS_esp))
-        << "File not found: " << FLAGS_esp;
-  }
-
-  if (SuperImageNeedsRebuilding(fetcher_config, *config)) {
-    bool success = RebuildSuperImage(fetcher_config, *config, FLAGS_super_image);
-    CHECK(success) << "Super image rebuilding requested but could not be completed.";
-  }
-
-  bool newDataImage = dataImageResult == DataImageResult::FileUpdated;
-
-  for (auto instance : config->Instances()) {
-    bool compositeMatchesDiskConfig = DoesCompositeMatchCurrentDiskConfig(
-        instance.PerInstancePath("os_composite_disk_config.txt"),
-        os_composite_disk_config(instance));
-    bool oldCompositeDisk = ShouldCreateCompositeDisk(
-        instance.os_composite_disk_path(), os_composite_disk_config(instance));
-    if (!compositeMatchesDiskConfig || oldCompositeDisk || !FLAGS_resume || newDataImage) {
-      if (FLAGS_resume) {
-        LOG(INFO) << "Requested to continue an existing session, (the default) "
-                  << "but the disk files have become out of date. Wiping the "
-                  << "old session files and starting a new session for device "
-                  << instance.serial_number();
-      }
-      CHECK(CreateCompositeDisk(*config, instance))
-          << "Failed to create composite disk";
+  auto os_disk_builder = OsCompositeDiskBuilder(config);
+  auto built_composite =
+      CF_EXPECT(os_disk_builder.BuildCompositeDiskIfNecessary());
+  if (built_composite) {
+    for (auto instance : config.Instances()) {
       if (FileExists(instance.access_kregistry_path())) {
-        CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none");
+        CF_EXPECT(CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */,
+                                   "none"),
+                  "Failed for \"" << instance.access_kregistry_path() << "\"");
+      }
+      if (FileExists(instance.hwcomposer_pmem_path())) {
+        CF_EXPECT(CreateBlankImage(instance.hwcomposer_pmem_path(), 2 /* mb */,
+                                   "none"),
+                  "Failed for \"" << instance.hwcomposer_pmem_path() << "\"");
       }
       if (FileExists(instance.pstore_path())) {
-        CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none");
+        CF_EXPECT(CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none"),
+                  "Failed for\"" << instance.pstore_path() << "\"");
       }
     }
   }
 
   if (!FLAGS_protected_vm) {
-    for (auto instance : config->Instances()) {
-      auto overlay_path = instance.PerInstancePath("overlay.img");
-      bool missingOverlay = !FileExists(overlay_path);
-      bool newOverlay = FileModificationTime(overlay_path) <
-                        FileModificationTime(instance.os_composite_disk_path());
-      if (missingOverlay || !FLAGS_resume || newOverlay) {
-        CreateQcowOverlay(config->crosvm_binary(),
-                          instance.os_composite_disk_path(), overlay_path);
+    for (auto instance : config.Instances()) {
+      os_disk_builder.OverlayPath(instance.PerInstancePath("overlay.img"));
+      CF_EXPECT(os_disk_builder.BuildOverlayIfNecessary());
+      if (instance.start_ap()) {
+        os_disk_builder.OverlayPath(instance.PerInstancePath("ap_overlay.img"));
+        CF_EXPECT(os_disk_builder.BuildOverlayIfNecessary());
       }
     }
   }
 
-  for (auto instance : config->Instances()) {
+  for (auto instance : config.Instances()) {
     // Check that the files exist
     for (const auto& file : instance.virtual_disk_paths()) {
       if (!file.empty()) {
-        CHECK(FileHasContent(file)) << "File not found: " << file;
+        CF_EXPECT(FileHasContent(file), "File not found: \"" << file << "\"");
       }
     }
+    // Gem5 Simulate per-instance what the bootloader would usually do
+    // Since on other devices this runs every time, just do it here every time
+    if (config.vm_manager() == Gem5Manager::name()) {
+      RepackGem5BootImage(
+          instance.PerInstancePath("initrd.img"),
+          instance.persistent_bootconfig_path(),
+          config.assembly_dir());
+    }
   }
+
+  return {};
 }
 
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/disk_flags.h b/host/commands/assemble_cvd/disk_flags.h
index 3491c3a..e75aa4f 100644
--- a/host/commands/assemble_cvd/disk_flags.h
+++ b/host/commands/assemble_cvd/disk_flags.h
@@ -20,14 +20,19 @@
 #include <memory>
 #include <vector>
 
+#include "common/libs/utils/result.h"
+#include "host/commands/assemble_cvd/disk_builder.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/fetcher_config.h"
+#include "host/libs/image_aggregator/image_aggregator.h"
 
 namespace cuttlefish {
 
-bool ResolveInstanceFiles();
-bool ShouldCreateAllCompositeDisks(const CuttlefishConfig& config);
-void CreateDynamicDiskFiles(const FetcherConfig& fetcher_config,
-                            const CuttlefishConfig* config);
+Result<void> ResolveInstanceFiles();
+
+Result<void> CreateDynamicDiskFiles(const FetcherConfig& fetcher_config,
+                                    const CuttlefishConfig& config);
+std::vector<ImagePartition> GetOsCompositeDiskConfig();
+DiskBuilder OsCompositeDiskBuilder(const CuttlefishConfig& config);
 
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/flag_feature.cpp b/host/commands/assemble_cvd/flag_feature.cpp
new file mode 100644
index 0000000..a81c4a1
--- /dev/null
+++ b/host/commands/assemble_cvd/flag_feature.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/commands/assemble_cvd/flag_feature.h"
+
+#include <android-base/strings.h>
+#include <fruit/fruit.h>
+#include <gflags/gflags.h>
+#include <string.h>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "host/libs/config/feature.h"
+
+namespace cuttlefish {
+
+static std::string XmlEscape(const std::string& s) {
+  using android::base::StringReplace;
+  return StringReplace(StringReplace(s, "<", "&lt;", true), ">", "&gt;", true);
+}
+
+class ParseGflagsImpl : public ParseGflags {
+ public:
+  INJECT(ParseGflagsImpl(ConfigFlag& config)) : config_(config) {}
+
+  std::string Name() const override { return "ParseGflags"; }
+  std::unordered_set<FlagFeature*> Dependencies() const override {
+    return {static_cast<FlagFeature*>(&config_)};
+  }
+  bool Process(std::vector<std::string>& args) override {
+    std::string process_name = "assemble_cvd";
+    std::vector<char*> pseudo_argv = {process_name.data()};
+    for (auto& arg : args) {
+      pseudo_argv.push_back(arg.data());
+    }
+    int argc = pseudo_argv.size();
+    auto argv = pseudo_argv.data();
+    gflags::AllowCommandLineReparsing();  // Support future non-gflags flags
+    gflags::ParseCommandLineNonHelpFlags(&argc, &argv,
+                                         /* remove_flags */ false);
+    return true;
+  }
+  bool WriteGflagsCompatHelpXml(std::ostream& out) const override {
+    // Lifted from external/gflags/src/gflags_reporting.cc:ShowXMLOfFlags
+    std::vector<gflags::CommandLineFlagInfo> flags;
+    gflags::GetAllFlags(&flags);
+    for (const auto& flag : flags) {
+      // From external/gflags/src/gflags_reporting.cc:DescribeOneFlagInXML
+      out << "<flag>\n";
+      out << "  <file>" << XmlEscape(flag.filename) << "</file>\n";
+      out << "  <name>" << XmlEscape(flag.name) << "</name>\n";
+      out << "  <meaning>" << XmlEscape(flag.description) << "</meaning>\n";
+      out << "  <default>" << XmlEscape(flag.default_value) << "</default>\n";
+      out << "  <current>" << XmlEscape(flag.current_value) << "</current>\n";
+      out << "  <type>" << XmlEscape(flag.type) << "</type>\n";
+      out << "</flag>\n";
+    }
+    return true;
+  }
+
+ private:
+  ConfigFlag& config_;
+};
+
+fruit::Component<fruit::Required<ConfigFlag>, ParseGflags> GflagsComponent() {
+  return fruit::createComponent()
+      .bind<ParseGflags, ParseGflagsImpl>()
+      .addMultibinding<FlagFeature, ParseGflags>();
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/assemble_cvd/flag_feature.h
similarity index 67%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/assemble_cvd/flag_feature.h
index 9f25445..da91a3d 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/assemble_cvd/flag_feature.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,16 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#pragma once
 
-#include "common/libs/utils/size_utils.h"
+#include <fruit/fruit.h>
 
-#include <unistd.h>
+#include "host/libs/config/config_flag.h"
+#include "host/libs/config/feature.h"
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+class ParseGflags : public FlagFeature {};
+
+fruit::Component<fruit::Required<ConfigFlag>, ParseGflags> GflagsComponent();
 
 }  // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index b12dfd2..08c2b68 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -19,15 +19,19 @@
 #include <sstream>
 #include <unordered_map>
 
+#include <fruit/fruit.h>
+
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
 #include "host/commands/assemble_cvd/alloc.h"
 #include "host/commands/assemble_cvd/boot_config.h"
-#include "host/commands/assemble_cvd/clean.h"
 #include "host/commands/assemble_cvd/disk_flags.h"
+#include "host/libs/config/config_flag.h"
 #include "host/libs/config/host_tools_version.h"
 #include "host/libs/graphics_detector/graphics_detector.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
+#include "host/libs/vm_manager/gem5_manager.h"
 #include "host/libs/vm_manager/qemu_manager.h"
 #include "host/libs/vm_manager/vm_manager.h"
 
@@ -36,12 +40,7 @@
 using cuttlefish::StringFromEnv;
 using cuttlefish::vm_manager::CrosvmManager;
 using google::FlagSettingMode::SET_FLAGS_DEFAULT;
-
-DEFINE_string(config, "phone",
-              "Config preset name. Will automatically set flag fields "
-              "using the values from this file of presets. See "
-              "device/google/cuttlefish/shared/config/config_*.json "
-              "for possible values.");
+using google::FlagSettingMode::SET_FLAGS_VALUE;
 
 DEFINE_int32(cpus, 2, "Virtual CPU count.");
 DEFINE_string(data_policy, "use_existing", "How to handle userdata partition."
@@ -49,8 +48,6 @@
             "'always_create'.");
 DEFINE_int32(blank_data_image_mb, 0,
              "The size of the blank data image to generate, MB.");
-DEFINE_string(blank_data_image_fmt, "f2fs",
-              "The fs format for the blank data image. Used with mkfs.");
 DEFINE_int32(gdb_port, 0,
              "Port number to spawn kernel gdb on e.g. -gdb_port=1234. The"
              "kernel must have been built with CONFIG_RANDOMIZE_BASE "
@@ -85,10 +82,12 @@
 DEFINE_string(initramfs_path, "", "Path to the initramfs");
 DEFINE_string(extra_kernel_cmdline, "",
               "Additional flags to put on the kernel command line");
+DEFINE_string(extra_bootconfig_args, "",
+              "Space-separated list of extra bootconfig args. "
+              "Note: overwriting an existing bootconfig argument "
+              "requires ':=' instead of '='.");
 DEFINE_bool(guest_enforce_security, true,
             "Whether to run in enforcing mode (non permissive).");
-DEFINE_bool(guest_audit_security, true,
-            "Whether to log security audits.");
 DEFINE_int32(memory_mb, 0, "Total amount of memory available for guest, MB.");
 DEFINE_string(serial_number, cuttlefish::ForCurrentInstance("CUTTLEFISHCVD"),
               "Serial number to use for the device");
@@ -99,14 +98,23 @@
 DEFINE_string(gpu_mode, cuttlefish::kGpuModeAuto,
               "What gpu configuration to use, one of {auto, drm_virgl, "
               "gfxstream, guest_swiftshader}");
+DEFINE_string(hwcomposer, cuttlefish::kHwComposerAuto,
+              "What hardware composer to use, one of {auto, drm, ranchu} ");
+DEFINE_string(gpu_capture_binary, "",
+              "Path to the GPU capture binary to use when capturing GPU traces"
+              "(ngfx, renderdoc, etc)");
+DEFINE_bool(enable_gpu_udmabuf,
+            false,
+            "Use the udmabuf driver for zero-copy virtio-gpu");
 
+DEFINE_bool(enable_gpu_angle,
+            false,
+            "Use ANGLE to provide GLES implementation (always true for"
+            " guest_swiftshader");
 DEFINE_bool(deprecated_boot_completed, false, "Log boot completed message to"
             " host kernel. This is only used during transition of our clients."
             " Will be deprecated soon.");
-DEFINE_bool(start_vnc_server, false, "Whether to start the vnc server process. "
-                                     "The VNC server runs at port 6443 + i for "
-                                     "the vsoc-i user or CUTTLEFISH_INSTANCE=i, "
-                                     "starting from 1.");
+
 DEFINE_bool(use_allocd, false,
             "Acquire static resources from the resource allocator daemon.");
 DEFINE_bool(enable_minimal_mode, false,
@@ -173,13 +181,17 @@
         false,
         "[Experimental] If enabled, exposes local adb service through a websocket.");
 
+static constexpr auto HOST_OPERATOR_SOCKET_PATH = "/run/cuttlefish/operator";
+
 DEFINE_bool(
-    start_webrtc_sig_server, false,
+    // The actual default for this flag is set with SetCommandLineOption() in
+    // GetKernelConfigsAndSetDefaults() at the end of this file.
+    start_webrtc_sig_server, true,
     "Whether to start the webrtc signaling server. This option only applies to "
     "the first instance, if multiple instances are launched they'll share the "
     "same signaling server, which is owned by the first one.");
 
-DEFINE_string(webrtc_sig_server_addr, "0.0.0.0",
+DEFINE_string(webrtc_sig_server_addr, "",
               "The address of the webrtc signaling server.");
 
 DEFINE_int32(
@@ -202,9 +214,13 @@
               "The path section of the URL where the device should be "
               "registered with the signaling server.");
 
+DEFINE_bool(webrtc_sig_server_secure, true,
+            "Whether the WebRTC signaling server uses secure protocols (WSS vs WS).");
+
 DEFINE_bool(verify_sig_server_certificate, false,
             "Whether to verify the signaling server's certificate with a "
-            "trusted signing authority (Disallow self signed certificates).");
+            "trusted signing authority (Disallow self signed certificates). "
+            "This is ignored if an insecure server is configured.");
 
 DEFINE_string(sig_server_headers_file, "",
               "Path to a file containing HTTP headers to be included in the "
@@ -217,25 +233,12 @@
     "appearance of the substring '{num}' in the device id will be substituted "
     "with the instance number to support multiple instances");
 
-DEFINE_string(adb_mode, "vsock_half_tunnel",
-              "Mode for ADB connection."
-              "'vsock_tunnel' for a TCP connection tunneled through vsock, "
-              "'native_vsock' for a  direct connection to the guest ADB over "
-              "vsock, 'vsock_half_tunnel' for a TCP connection forwarded to "
-              "the guest ADB server, or a comma separated list of types as in "
-              "'native_vsock,vsock_half_tunnel'");
-DEFINE_bool(run_adb_connector, !cuttlefish::IsRunningInContainer(),
-            "Maintain adb connection by sending 'adb connect' commands to the "
-            "server. Only relevant with -adb_mode=tunnel or vsock_tunnel");
-
 DEFINE_string(uuid, cuttlefish::ForCurrentInstance(cuttlefish::kDefaultUuidPrefix),
               "UUID to use for the device. Random if not specified");
 DEFINE_bool(daemon, false,
             "Run cuttlefish in background, the launcher exits on boot "
             "completed/failed");
 
-DEFINE_string(device_title, "", "Human readable name for the instance, "
-              "used by the vnc_server for its server title");
 DEFINE_string(setupwizard_mode, "DISABLED",
             "One of DISABLED,OPTIONAL,REQUIRED");
 
@@ -243,21 +246,11 @@
               "Path to the directory containing the qemu binary to use");
 DEFINE_string(crosvm_binary, HostBinaryPath("crosvm"),
               "The Crosvm binary to use");
-DEFINE_string(tpm_device, "", "A host TPM device to pass through commands to.");
+DEFINE_string(gem5_binary_dir, HostBinaryPath("gem5"),
+              "Path to the gem5 build tree root");
 DEFINE_bool(restart_subprocesses, true, "Restart any crashed host process");
 DEFINE_bool(enable_vehicle_hal_grpc_server, true, "Enables the vehicle HAL "
             "emulation gRPC server on the host");
-DEFINE_string(custom_action_config, "",
-              "Path to a custom action config JSON. Defaults to the file provided by "
-              "build variable CVD_CUSTOM_ACTION_CONFIG. If this build variable "
-              "is empty then the custom action config will be empty as well.");
-DEFINE_string(custom_actions, "",
-              "Serialized JSON of an array of custom action objects (in the "
-              "same format as custom action config JSON files). For use "
-              "within --config preset config files; prefer "
-              "--custom_action_config to specify a custom config file on the "
-              "command line. Actions in this flag are combined with actions "
-              "in --custom_action_config.");
 DEFINE_string(bootloader, "", "Bootloader binary path");
 DEFINE_string(boot_slot, "", "Force booting into the given slot. If empty, "
              "the slot will be chosen based on the misc partition if using a "
@@ -287,6 +280,21 @@
 
 DEFINE_bool(vhost_net, false, "Enable vhost acceleration of networking");
 
+DEFINE_string(
+    vhost_user_mac80211_hwsim, "",
+    "Unix socket path for vhost-user of mac80211_hwsim, typically served by "
+    "wmediumd. You can set this when using an external wmediumd instance.");
+DEFINE_string(wmediumd_config, "",
+              "Path to the wmediumd config file. When missing, the default "
+              "configuration is used which adds MAC addresses for up to 16 "
+              "cuttlefish instances including AP.");
+DEFINE_string(ap_rootfs_image,
+              DefaultHostArtifactsPath("etc/openwrt/images/openwrt_rootfs"),
+              "rootfs image for AP instance");
+DEFINE_string(ap_kernel_image,
+              DefaultHostArtifactsPath("etc/openwrt/images/kernel_for_openwrt"),
+              "kernel image for AP instance");
+
 DEFINE_bool(record_screen, false, "Enable screen recording. "
                                   "Requires --start_webrtc");
 
@@ -321,20 +329,19 @@
 
 DEFINE_uint32(camera_server_port, 0, "camera vsock port");
 
+DEFINE_string(userdata_format, "f2fs", "The userdata filesystem format");
+
 DECLARE_string(assembly_dir);
 DECLARE_string(boot_image);
 DECLARE_string(system_image_dir);
 
 namespace cuttlefish {
 using vm_manager::QemuManager;
+using vm_manager::Gem5Manager;
 using vm_manager::GetVmManager;
 
 namespace {
 
-bool IsFlagSet(const std::string& flag) {
-  return !gflags::GetCommandLineFlagInfoOrDie(flag.c_str()).is_default;
-}
-
 std::pair<uint16_t, uint16_t> ParsePortRange(const std::string& flag) {
   static const std::regex rgx("[0-9]+:[0-9]+");
   CHECK(std::regex_match(flag, rgx))
@@ -348,11 +355,6 @@
   return port_range;
 }
 
-int NumStreamers() {
-  auto start_flags = {FLAGS_start_vnc_server, FLAGS_start_webrtc};
-  return std::count(start_flags.begin(), start_flags.end(), true);
-}
-
 std::string StrForInstance(const std::string& prefix, int num) {
   std::ostringstream stream;
   stream << prefix << std::setfill('0') << std::setw(2) << num;
@@ -414,13 +416,15 @@
 }
 
 #ifdef __ANDROID__
-void ReadKernelConfig(KernelConfig* kernel_config) {
+Result<KernelConfig> ReadKernelConfig() {
   // QEMU isn't on Android, so always follow host arch
-  kernel_config->target_arch = HostArch();
-  kernel_config->bootconfig_supported = true;
+  KernelConfig ret{};
+  ret.target_arch = HostArch();
+  ret.bootconfig_supported = true;
+  return ret;
 }
 #else
-void ReadKernelConfig(KernelConfig* kernel_config) {
+Result<KernelConfig> ReadKernelConfig() {
   // extract-ikconfig can be called directly on the boot image since it looks
   // for the ikconfig header in the image before extracting the config list.
   // This code is liable to break if the boot image ever includes the
@@ -438,44 +442,50 @@
   std::string ikconfig_path =
       StringFromEnv("TEMP", "/tmp") + "/ikconfig.XXXXXX";
   auto ikconfig_fd = SharedFD::Mkstemp(&ikconfig_path);
-  CHECK(ikconfig_fd->IsOpen())
-      << "Unable to create ikconfig file: " << ikconfig_fd->StrError();
+  CF_EXPECT(ikconfig_fd->IsOpen(),
+            "Unable to create ikconfig file: " << ikconfig_fd->StrError());
   ikconfig_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, ikconfig_fd);
 
   auto ikconfig_proc = ikconfig_cmd.Start();
-  CHECK(ikconfig_proc.Started() && ikconfig_proc.Wait() == 0)
-      << "Failed to extract ikconfig from " << kernel_image_path;
+  CF_EXPECT(ikconfig_proc.Started() && ikconfig_proc.Wait() == 0,
+            "Failed to extract ikconfig from " << kernel_image_path);
 
   std::string config = ReadFile(ikconfig_path);
 
+  KernelConfig kernel_config;
   if (config.find("\nCONFIG_ARM=y") != std::string::npos) {
-    kernel_config->target_arch = Arch::Arm;
+    kernel_config.target_arch = Arch::Arm;
   } else if (config.find("\nCONFIG_ARM64=y") != std::string::npos) {
-    kernel_config->target_arch = Arch::Arm64;
+    kernel_config.target_arch = Arch::Arm64;
   } else if (config.find("\nCONFIG_X86_64=y") != std::string::npos) {
-    kernel_config->target_arch = Arch::X86_64;
+    kernel_config.target_arch = Arch::X86_64;
   } else if (config.find("\nCONFIG_X86=y") != std::string::npos) {
-    kernel_config->target_arch = Arch::X86;
+    kernel_config.target_arch = Arch::X86;
   } else {
-    LOG(FATAL) << "Unknown target architecture";
+    return CF_ERR("Unknown target architecture");
   }
-  kernel_config->bootconfig_supported =
+  kernel_config.bootconfig_supported =
       config.find("\nCONFIG_BOOT_CONFIG=y") != std::string::npos;
 
   unlink(ikconfig_path.c_str());
+  return kernel_config;
 }
 #endif  // #ifdef __ANDROID__
 
 } // namespace
 
 CuttlefishConfig InitializeCuttlefishConfiguration(
-    const std::string& instance_dir, int modem_simulator_count,
-    KernelConfig kernel_config) {
-  // At most one streamer can be started.
-  CHECK(NumStreamers() <= 1);
-
+    const std::string& root_dir, int modem_simulator_count,
+    KernelConfig kernel_config, fruit::Injector<>& injector) {
   CuttlefishConfig tmp_config_obj;
-  tmp_config_obj.set_assembly_dir(FLAGS_assembly_dir);
+
+  for (const auto& fragment : injector.getMultibindings<ConfigFragment>()) {
+    CHECK(tmp_config_obj.SaveFragment(*fragment))
+        << "Failed to save fragment " << fragment->Name();
+  }
+
+  tmp_config_obj.set_root_dir(root_dir);
+
   tmp_config_obj.set_target_arch(kernel_config.target_arch);
   tmp_config_obj.set_bootconfig_supported(kernel_config.bootconfig_supported);
   auto vmm = GetVmManager(FLAGS_vm_manager, kernel_config.target_arch);
@@ -521,10 +531,9 @@
   const GraphicsAvailability graphics_availability =
     GetGraphicsAvailabilityWithSubprocessCheck();
 
-  LOG(VERBOSE) << graphics_availability;
+  LOG(DEBUG) << graphics_availability;
 
   tmp_config_obj.set_gpu_mode(FLAGS_gpu_mode);
-
   if (tmp_config_obj.gpu_mode() != kGpuModeAuto &&
       tmp_config_obj.gpu_mode() != kGpuModeDrmVirgl &&
       tmp_config_obj.gpu_mode() != kGpuModeGfxStream &&
@@ -559,15 +568,57 @@
                     "--gpu_mode=auto or --gpu_mode=guest_swiftshader.";
     }
   }
+
+  tmp_config_obj.set_restart_subprocesses(FLAGS_restart_subprocesses);
+  tmp_config_obj.set_gpu_capture_binary(FLAGS_gpu_capture_binary);
+  if (!tmp_config_obj.gpu_capture_binary().empty()) {
+    CHECK(tmp_config_obj.gpu_mode() == kGpuModeGfxStream)
+        << "GPU capture only supported with --gpu_mode=gfxstream";
+
+    // GPU capture runs in a detached mode where the "launcher" process
+    // intentionally exits immediately.
+    CHECK(!tmp_config_obj.restart_subprocesses())
+        << "GPU capture only supported with --norestart_subprocesses";
+  }
+
+  tmp_config_obj.set_hwcomposer(FLAGS_hwcomposer);
+  if (!tmp_config_obj.hwcomposer().empty()) {
+    if (tmp_config_obj.hwcomposer() == kHwComposerRanchu) {
+      CHECK(tmp_config_obj.gpu_mode() != kGpuModeDrmVirgl)
+        << "ranchu hwcomposer not supported with --gpu_mode=drm_virgl";
+    }
+  }
+
+  if (tmp_config_obj.hwcomposer() == kHwComposerAuto) {
+      if (tmp_config_obj.gpu_mode() == kGpuModeDrmVirgl) {
+        tmp_config_obj.set_hwcomposer(kHwComposerDrm);
+      } else {
+        tmp_config_obj.set_hwcomposer(kHwComposerRanchu);
+      }
+  }
+
+  // The device needs to avoid having both hwcomposer2.4 and hwcomposer3
+  // services running at the same time so warn the user to manually build
+  // in drm_hwcomposer when needed.
+  if (tmp_config_obj.hwcomposer() == kHwComposerAuto) {
+    LOG(WARNING) << "In order to run with --hwcomposer=drm. Please make sure "
+                    "Cuttlefish was built with "
+                    "TARGET_ENABLE_DRMHWCOMPOSER=true.";
+  }
+
+  tmp_config_obj.set_enable_gpu_udmabuf(FLAGS_enable_gpu_udmabuf);
+  tmp_config_obj.set_enable_gpu_angle(FLAGS_enable_gpu_angle);
+
   // Sepolicy rules need to be updated to support gpu mode. Temporarily disable
   // auto-enabling sandbox when gpu is enabled (b/152323505).
   if (tmp_config_obj.gpu_mode() != kGpuModeGuestSwiftshader) {
     SetCommandLineOptionWithMode("enable_sandbox", "false", SET_FLAGS_DEFAULT);
   }
 
-  if (vmm->ConfigureGpuMode(tmp_config_obj.gpu_mode()).empty()) {
-    LOG(FATAL) << "Invalid gpu_mode=" << FLAGS_gpu_mode <<
-               " does not work with vm_manager=" << FLAGS_vm_manager;
+  if (vmm->ConfigureGraphics(tmp_config_obj).empty()) {
+    LOG(FATAL) << "Invalid (gpu_mode=," << FLAGS_gpu_mode <<
+               " hwcomposer= " << FLAGS_hwcomposer <<
+               ") does not work with vm_manager=" << FLAGS_vm_manager;
   }
 
   CHECK(!FLAGS_smt || FLAGS_cpus % 2 == 0)
@@ -585,12 +636,9 @@
 
   tmp_config_obj.set_gdb_port(FLAGS_gdb_port);
 
-  std::vector<std::string> adb = android::base::Split(FLAGS_adb_mode, ",");
-  tmp_config_obj.set_adb_mode(std::set<std::string>(adb.begin(), adb.end()));
-
   tmp_config_obj.set_guest_enforce_security(FLAGS_guest_enforce_security);
-  tmp_config_obj.set_guest_audit_security(FLAGS_guest_audit_security);
   tmp_config_obj.set_extra_kernel_cmdline(FLAGS_extra_kernel_cmdline);
+  tmp_config_obj.set_extra_bootconfig_args(FLAGS_extra_bootconfig_args);
 
   if (FLAGS_console) {
     SetCommandLineOptionWithMode("enable_sandbox", "false", SET_FLAGS_DEFAULT);
@@ -605,15 +653,14 @@
 
   tmp_config_obj.set_qemu_binary_dir(FLAGS_qemu_binary_dir);
   tmp_config_obj.set_crosvm_binary(FLAGS_crosvm_binary);
-  tmp_config_obj.set_tpm_device(FLAGS_tpm_device);
-
-  tmp_config_obj.set_enable_vnc_server(FLAGS_start_vnc_server);
+  tmp_config_obj.set_gem5_binary_dir(FLAGS_gem5_binary_dir);
 
   tmp_config_obj.set_seccomp_policy_dir(FLAGS_seccomp_policy_dir);
 
   tmp_config_obj.set_enable_webrtc(FLAGS_start_webrtc);
   tmp_config_obj.set_webrtc_assets_dir(FLAGS_webrtc_assets_dir);
   tmp_config_obj.set_webrtc_certs_dir(FLAGS_webrtc_certs_dir);
+  tmp_config_obj.set_sig_server_secure(FLAGS_webrtc_sig_server_secure);
   // Note: This will be overridden if the sig server is started by us
   tmp_config_obj.set_sig_server_port(FLAGS_webrtc_sig_server_port);
   tmp_config_obj.set_sig_server_address(FLAGS_webrtc_sig_server_addr);
@@ -634,71 +681,15 @@
   tmp_config_obj.set_webrtc_enable_adb_websocket(
           FLAGS_webrtc_enable_adb_websocket);
 
-  tmp_config_obj.set_restart_subprocesses(FLAGS_restart_subprocesses);
-  tmp_config_obj.set_run_adb_connector(FLAGS_run_adb_connector);
   tmp_config_obj.set_run_as_daemon(FLAGS_daemon);
 
   tmp_config_obj.set_data_policy(FLAGS_data_policy);
   tmp_config_obj.set_blank_data_image_mb(FLAGS_blank_data_image_mb);
-  tmp_config_obj.set_blank_data_image_fmt(FLAGS_blank_data_image_fmt);
 
   tmp_config_obj.set_enable_gnss_grpc_proxy(FLAGS_start_gnss_proxy);
 
-  tmp_config_obj.set_enable_vehicle_hal_grpc_server(FLAGS_enable_vehicle_hal_grpc_server);
-  tmp_config_obj.set_vehicle_hal_grpc_server_binary(
-      HostBinaryPath("android.hardware.automotive.vehicle@2.0-virtualization-grpc-server"));
-
-  std::string custom_action_config;
-  if (!FLAGS_custom_action_config.empty()) {
-    custom_action_config = FLAGS_custom_action_config;
-  } else {
-    std::string custom_action_config_dir =
-        DefaultHostArtifactsPath("etc/cvd_custom_action_config");
-    if (DirectoryExists(custom_action_config_dir)) {
-      auto custom_action_configs = DirectoryContents(custom_action_config_dir);
-      // Two entries are always . and ..
-      if (custom_action_configs.size() > 3) {
-        LOG(ERROR) << "Expected at most one custom action config in "
-                   << custom_action_config_dir << ". Please delete extras.";
-      } else if (custom_action_configs.size() == 3) {
-        for (const auto& config : custom_action_configs) {
-          if (android::base::EndsWithIgnoreCase(config, ".json")) {
-            custom_action_config = custom_action_config_dir + "/" + config;
-          }
-        }
-      }
-    }
-  }
-  std::vector<CustomActionConfig> custom_actions;
-  Json::CharReaderBuilder builder;
-  Json::Value custom_action_array(Json::arrayValue);
-  if (custom_action_config != "") {
-    // Load the custom action config JSON.
-    std::ifstream ifs(custom_action_config);
-    std::string errorMessage;
-    if (!Json::parseFromStream(builder, ifs, &custom_action_array, &errorMessage)) {
-      LOG(FATAL) << "Could not read custom actions config file "
-                 << custom_action_config << ": "
-                 << errorMessage;
-    }
-    for (const auto& custom_action : custom_action_array) {
-      custom_actions.push_back(CustomActionConfig(custom_action));
-    }
-  }
-  if (FLAGS_custom_actions != "") {
-    // Load the custom action from the --config preset file.
-    std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
-    std::string errorMessage;
-    if (!reader->parse(&*FLAGS_custom_actions.begin(), &*FLAGS_custom_actions.end(),
-                       &custom_action_array, &errorMessage)) {
-      LOG(FATAL) << "Could not read custom actions config flag: "
-                 << errorMessage;
-    }
-    for (const auto& custom_action : custom_action_array) {
-      custom_actions.push_back(CustomActionConfig(custom_action));
-    }
-  }
-  tmp_config_obj.set_custom_actions(custom_actions);
+  tmp_config_obj.set_enable_vehicle_hal_grpc_server(
+      FLAGS_enable_vehicle_hal_grpc_server);
 
   tmp_config_obj.set_bootloader(FLAGS_bootloader);
 
@@ -716,12 +707,34 @@
 
   tmp_config_obj.set_vhost_net(FLAGS_vhost_net);
 
+  tmp_config_obj.set_vhost_user_mac80211_hwsim(FLAGS_vhost_user_mac80211_hwsim);
+
+  if ((FLAGS_ap_rootfs_image.empty()) != (FLAGS_ap_kernel_image.empty())) {
+    LOG(FATAL) << "Either both ap_rootfs_image and ap_kernel_image should be "
+                  "set or neither should be set.";
+  }
+
+  tmp_config_obj.set_ap_rootfs_image(FLAGS_ap_rootfs_image);
+  tmp_config_obj.set_ap_kernel_image(FLAGS_ap_kernel_image);
+
+  tmp_config_obj.set_wmediumd_config(FLAGS_wmediumd_config);
+
+  tmp_config_obj.set_rootcanal_hci_port(7300);
+  tmp_config_obj.set_rootcanal_link_port(7400);
+  tmp_config_obj.set_rootcanal_test_port(7500);
+  tmp_config_obj.set_rootcanal_config_file(
+      FLAGS_bluetooth_controller_properties_file);
+  tmp_config_obj.set_rootcanal_default_commands_file(
+      FLAGS_bluetooth_default_commands_file);
+
   tmp_config_obj.set_record_screen(FLAGS_record_screen);
 
   tmp_config_obj.set_enable_host_bluetooth(FLAGS_enable_host_bluetooth);
 
   tmp_config_obj.set_protected_vm(FLAGS_protected_vm);
 
+  tmp_config_obj.set_userdata_format(FLAGS_userdata_format);
+
   std::vector<int> num_instances;
   for (int i = 0; i < FLAGS_num_instances; i++) {
     num_instances.push_back(GetInstance() + i);
@@ -743,10 +756,7 @@
 
     auto instance = tmp_config_obj.ForInstance(num);
     auto const_instance =
-        const_cast<const CuttlefishConfig&>(tmp_config_obj)
-            .ForInstance(num);
-    // Set this first so that calls to PerInstancePath below are correct
-    instance.set_instance_dir(instance_dir + "." + std::to_string(num));
+        const_cast<const CuttlefishConfig&>(tmp_config_obj).ForInstance(num);
     instance.set_use_allocd(FLAGS_use_allocd);
     if (FLAGS_use_random_serial) {
       instance.set_serial_number(
@@ -770,17 +780,19 @@
 
     instance.set_uuid(FLAGS_uuid);
 
-    instance.set_vnc_server_port(6444 + num - 1);
-    instance.set_host_port(6520 + num - 1);
+    instance.set_modem_simulator_host_id(1000 + num);  // Must be 4 digits
+    // the deprecated vnc was 6444 + num - 1, and qemu_vnc was vnc - 5900
+    instance.set_qemu_vnc_server_port(544 + num - 1);
+    instance.set_adb_host_port(6520 + num - 1);
     instance.set_adb_ip_and_port("0.0.0.0:" + std::to_string(6520 + num - 1));
+    instance.set_confui_host_vsock_port(7700 + num - 1);
     instance.set_tombstone_receiver_port(calc_vsock_port(6600));
-    instance.set_vehicle_hal_server_port(9210 + num - 1);
+    instance.set_vehicle_hal_server_port(9300 + num - 1);
     instance.set_audiocontrol_server_port(9410);  /* OK to use the same port number across instances */
     instance.set_config_server_port(calc_vsock_port(6800));
 
     if (tmp_config_obj.gpu_mode() != kGpuModeDrmVirgl &&
         tmp_config_obj.gpu_mode() != kGpuModeGfxStream) {
-        instance.set_frames_server_port(calc_vsock_port(6900));
       if (FLAGS_vm_manager == QemuManager::name()) {
         instance.set_keyboard_server_port(calc_vsock_port(7000));
         instance.set_touch_server_port(calc_vsock_port(7100));
@@ -793,39 +805,33 @@
       instance.set_gnss_file_path(gnss_file_paths[num-1]);
     }
 
-    instance.set_rootcanal_hci_port(7300 + num - 1);
-    instance.set_rootcanal_link_port(7400 + num - 1);
-    instance.set_rootcanal_test_port(7500 + num - 1);
-    instance.set_rootcanal_config_file(
-        FLAGS_bluetooth_controller_properties_file);
-    instance.set_rootcanal_default_commands_file(
-        FLAGS_bluetooth_default_commands_file);
-
     instance.set_camera_server_port(FLAGS_camera_server_port);
-    instance.set_device_title(FLAGS_device_title);
 
     if (FLAGS_protected_vm) {
       instance.set_virtual_disk_paths(
           {const_instance.PerInstancePath("os_composite.img")});
     } else {
       std::vector<std::string> virtual_disk_paths = {
-          const_instance.PerInstancePath("overlay.img"),
           const_instance.PerInstancePath("persistent_composite.img"),
       };
+      if (FLAGS_vm_manager != Gem5Manager::name()) {
+        virtual_disk_paths.insert(virtual_disk_paths.begin(),
+            const_instance.PerInstancePath("overlay.img"));
+      } else {
+        // Gem5 already uses CoW wrappers around disk images
+        virtual_disk_paths.insert(virtual_disk_paths.begin(),
+            tmp_config_obj.os_composite_disk_path());
+      }
       if (FLAGS_use_sdcard) {
         virtual_disk_paths.push_back(const_instance.sdcard_path());
       }
       instance.set_virtual_disk_paths(virtual_disk_paths);
     }
 
-    std::array<unsigned char, 6> mac_address;
-    mac_address[0] = 1 << 6; // locally administered
-    // TODO(schuffelen): Randomize these and preserve the state.
-    for (int i = 1; i < 5; i++) {
-      mac_address[i] = i;
-    }
-    mac_address[5] = num;
-    instance.set_wifi_mac_address(mac_address);
+    // We'd like to set mac prefix to be 5554, 5555, 5556, ... in normal cases.
+    // When --base_instance_num=3, this might be 5556, 5557, 5558, ... (skipping
+    // first two)
+    instance.set_wifi_mac_prefix(5554 + (num - 1));
 
     instance.set_start_webrtc_signaling_server(false);
 
@@ -840,14 +846,45 @@
       }
       instance.set_webrtc_device_id(device_id);
     }
-    if (FLAGS_start_webrtc_sig_server && is_first_instance) {
+    if (!is_first_instance || !FLAGS_start_webrtc) {
+      // Only the first instance starts the signaling server or proxy
+      instance.set_start_webrtc_signaling_server(false);
+      instance.set_start_webrtc_sig_server_proxy(false);
+    } else {
       auto port = 8443 + num - 1;
       // Change the signaling server port for all instances
       tmp_config_obj.set_sig_server_port(port);
-      instance.set_start_webrtc_signaling_server(true);
-    } else {
-      instance.set_start_webrtc_signaling_server(false);
+      // Either the signaling server or the proxy is started, never both
+      instance.set_start_webrtc_signaling_server(FLAGS_start_webrtc_sig_server);
+      // The proxy is only started if the host operator is available
+      instance.set_start_webrtc_sig_server_proxy(
+          cuttlefish::FileIsSocket(HOST_OPERATOR_SOCKET_PATH) &&
+          !FLAGS_start_webrtc_sig_server);
     }
+
+    // Start wmediumd process for the first instance if
+    // vhost_user_mac80211_hwsim is not specified.
+    const bool start_wmediumd =
+        FLAGS_vhost_user_mac80211_hwsim.empty() && is_first_instance;
+    if (start_wmediumd) {
+      // TODO(b/199020470) move this to the directory for shared resources
+      auto vhost_user_socket_path =
+          const_instance.PerInstanceInternalPath("vhost_user_mac80211");
+      auto wmediumd_api_socket_path =
+          const_instance.PerInstanceInternalPath("wmediumd_api_server");
+
+      tmp_config_obj.set_vhost_user_mac80211_hwsim(vhost_user_socket_path);
+      tmp_config_obj.set_wmediumd_api_server_socket(wmediumd_api_socket_path);
+      instance.set_start_wmediumd(true);
+    } else {
+      instance.set_start_wmediumd(false);
+    }
+
+    instance.set_start_rootcanal(is_first_instance);
+
+    instance.set_start_ap(!FLAGS_ap_rootfs_image.empty() &&
+                          !FLAGS_ap_kernel_image.empty() && is_first_instance);
+
     is_first_instance = false;
 
     // instance.modem_simulator_ports := "" or "[port,]*port"
@@ -866,112 +903,61 @@
     }
   } // end of num_instances loop
 
+  std::vector<std::string> names;
+  for (const auto& instance : tmp_config_obj.Instances()) {
+    names.emplace_back(instance.instance_name());
+  }
+  tmp_config_obj.set_instance_names(names);
+
   tmp_config_obj.set_enable_sandbox(FLAGS_enable_sandbox);
 
-  // Audio is not available for VNC server
+  // Audio is not available for Arm64
   SetCommandLineOptionWithMode(
       "enable_audio",
-      (FLAGS_start_vnc_server || (cuttlefish::HostArch() == cuttlefish::Arch::Arm64))
-          ? "false"
-          : "true",
+      (cuttlefish::HostArch() == cuttlefish::Arch::Arm64) ? "false" : "true",
       SET_FLAGS_DEFAULT);
   tmp_config_obj.set_enable_audio(FLAGS_enable_audio);
 
   return tmp_config_obj;
 }
 
-void SetDefaultFlagsFromConfigPreset() {
-  std::string config_preset = FLAGS_config;  // The name of the preset config.
-  std::string config_file_path;  // The path to the preset config JSON.
-  std::set<std::string> allowed_config_presets;
-  for (const std::string& file :
-       DirectoryContents(DefaultHostArtifactsPath("etc/cvd_config"))) {
-    std::string_view local_file(file);
-    if (android::base::ConsumePrefix(&local_file, "cvd_config_") &&
-        android::base::ConsumeSuffix(&local_file, ".json")) {
-      allowed_config_presets.emplace(local_file);
-    }
-  }
-
-  // If the user specifies a --config name, then use that config
-  // preset option.
-  std::string android_info_path = FLAGS_system_image_dir + "/android-info.txt";
-  if (IsFlagSet("config")) {
-    if (!allowed_config_presets.count(config_preset)) {
-      LOG(FATAL) << "Invalid --config option '" << config_preset
-                 << "'. Valid options: "
-                 << android::base::Join(allowed_config_presets, ",");
-    }
-  } else if (FileExists(android_info_path)) {
-    // Otherwise try to load the correct preset using android-info.txt.
-    std::ifstream ifs(android_info_path);
-    if (ifs.is_open()) {
-      std::string android_info;
-      ifs >> android_info;
-      std::string_view local_android_info(android_info);
-      if (android::base::ConsumePrefix(&local_android_info, "config=")) {
-        config_preset = local_android_info;
-      }
-      if (!allowed_config_presets.count(config_preset)) {
-        LOG(WARNING) << android_info_path
-                     << " contains invalid config preset: '"
-                     << local_android_info << "'. Defaulting to 'phone'.";
-        config_preset = "phone";
-      }
-    }
-  }
-  LOG(INFO) << "Launching CVD using --config='" << config_preset << "'.";
-
-  config_file_path = DefaultHostArtifactsPath("etc/cvd_config/cvd_config_" +
-                                              config_preset + ".json");
-  Json::Value config;
-  Json::CharReaderBuilder builder;
-  std::ifstream ifs(config_file_path);
-  std::string errorMessage;
-  if (!Json::parseFromStream(builder, ifs, &config, &errorMessage)) {
-    LOG(FATAL) << "Could not read config file " << config_file_path << ": "
-               << errorMessage;
-  }
-  for (const std::string& flag : config.getMemberNames()) {
-    std::string value;
-    if (flag == "custom_actions") {
-      Json::StreamWriterBuilder factory;
-      value = Json::writeString(factory, config[flag]);
-    } else {
-      value = config[flag].asString();
-    }
-    if (gflags::SetCommandLineOptionWithMode(flag.c_str(), value.c_str(),
-                                             SET_FLAGS_DEFAULT)
-            .empty()) {
-      LOG(FATAL) << "Error setting flag '" << flag << "'.";
-    }
-  }
-}
-
-void SetDefaultFlagsForQemu() {
+void SetDefaultFlagsForQemu(Arch target_arch) {
   // for now, we don't set non-default options for QEMU
-  if (FLAGS_gpu_mode == kGpuModeGuestSwiftshader && NumStreamers() == 0) {
+  if (FLAGS_gpu_mode == kGpuModeGuestSwiftshader && !FLAGS_start_webrtc) {
     // This makes WebRTC the default streamer unless the user requests
     // another via a --star_<streamer> flag, while at the same time it's
     // possible to run without any streamer by setting --start_webrtc=false.
     SetCommandLineOptionWithMode("start_webrtc", "true", SET_FLAGS_DEFAULT);
   }
-  std::string default_bootloader = FLAGS_system_image_dir + "/bootloader.qemu";
+  std::string default_bootloader =
+      DefaultHostArtifactsPath("etc/bootloader_");
+  if(target_arch == Arch::Arm) {
+      // Bootloader is unstable >512MB RAM on 32-bit ARM
+      SetCommandLineOptionWithMode("memory_mb", "512", SET_FLAGS_VALUE);
+      default_bootloader += "arm";
+  } else if (target_arch == Arch::Arm64) {
+      default_bootloader += "aarch64";
+  } else {
+      default_bootloader += "x86_64";
+  }
+  default_bootloader += "/bootloader.qemu";
   SetCommandLineOptionWithMode("bootloader", default_bootloader.c_str(),
                                SET_FLAGS_DEFAULT);
 }
 
 void SetDefaultFlagsForCrosvm() {
-  if (NumStreamers() == 0) {
+  if (!FLAGS_start_webrtc) {
     // This makes WebRTC the default streamer unless the user requests
     // another via a --star_<streamer> flag, while at the same time it's
     // possible to run without any streamer by setting --start_webrtc=false.
     SetCommandLineOptionWithMode("start_webrtc", "true", SET_FLAGS_DEFAULT);
   }
 
-  // TODO(b/182484563): Re-enable autodetection when we fix the crosvm crashes
-  bool default_enable_sandbox = false;
-
+  std::set<Arch> supported_archs{Arch::X86_64};
+  bool default_enable_sandbox =
+      supported_archs.find(HostArch()) != supported_archs.end() &&
+      EnsureDirectoryExists(kCrosvmVarEmptyDir).ok() &&
+      IsDirectoryEmpty(kCrosvmVarEmptyDir) && !IsRunningInContainer();
   SetCommandLineOptionWithMode("enable_sandbox",
                                (default_enable_sandbox ? "true" : "false"),
                                SET_FLAGS_DEFAULT);
@@ -981,19 +967,21 @@
                                SET_FLAGS_DEFAULT);
 }
 
-bool ParseCommandLineFlags(int* argc, char*** argv, KernelConfig* kernel_config) {
-  google::ParseCommandLineNonHelpFlags(argc, argv, true);
-  SetDefaultFlagsFromConfigPreset();
-  google::HandleCommandLineHelpFlags();
-  bool invalid_manager = false;
+void SetDefaultFlagsForGem5() {
+  // TODO: Add support for gem5 gpu models
+  SetCommandLineOptionWithMode("gpu_mode", kGpuModeGuestSwiftshader,
+                               SET_FLAGS_DEFAULT);
 
-  if (!ResolveInstanceFiles()) {
-    return false;
-  }
+  SetCommandLineOptionWithMode("cpus", "1", SET_FLAGS_DEFAULT);
+}
 
-  ReadKernelConfig(kernel_config);
+Result<KernelConfig> GetKernelConfigAndSetDefaults() {
+  CF_EXPECT(ResolveInstanceFiles(), "Failed to resolve instance files");
+
+  KernelConfig kernel_config = CF_EXPECT(ReadKernelConfig());
+
   if (FLAGS_vm_manager == "") {
-    if (IsHostCompatible(kernel_config->target_arch)) {
+    if (IsHostCompatible(kernel_config.target_arch)) {
       FLAGS_vm_manager = CrosvmManager::name();
     } else {
       FLAGS_vm_manager = QemuManager::name();
@@ -1001,26 +989,36 @@
   }
 
   if (FLAGS_vm_manager == QemuManager::name()) {
-    SetDefaultFlagsForQemu();
+    SetDefaultFlagsForQemu(kernel_config.target_arch);
   } else if (FLAGS_vm_manager == CrosvmManager::name()) {
     SetDefaultFlagsForCrosvm();
+  } else if (FLAGS_vm_manager == Gem5Manager::name()) {
+    // TODO: Get the other architectures working
+    if (kernel_config.target_arch != Arch::Arm64) {
+      return CF_ERR("Gem5 only supports ARM64");
+    }
+    SetDefaultFlagsForGem5();
   } else {
-    std::cerr << "Unknown Virtual Machine Manager: " << FLAGS_vm_manager
-              << std::endl;
-    invalid_manager = true;
+    return CF_ERR("Unknown Virtual Machine Manager: " << FLAGS_vm_manager);
   }
-  // The default for starting signaling server is whether or not webrt is to be
-  // started.
-  SetCommandLineOptionWithMode("start_webrtc_sig_server",
-                               FLAGS_start_webrtc ? "true" : "false",
-                               SET_FLAGS_DEFAULT);
-  if (invalid_manager) {
-    return false;
+  if (FLAGS_vm_manager != Gem5Manager::name()) {
+    auto host_operator_present =
+        cuttlefish::FileIsSocket(HOST_OPERATOR_SOCKET_PATH);
+    // The default for starting signaling server depends on whether or not webrtc
+    // is to be started and the presence of the host orchestrator.
+    SetCommandLineOptionWithMode(
+        "start_webrtc_sig_server",
+        FLAGS_start_webrtc && !host_operator_present ? "true" : "false",
+        SET_FLAGS_DEFAULT);
+    SetCommandLineOptionWithMode(
+        "webrtc_sig_server_addr",
+        host_operator_present ? HOST_OPERATOR_SOCKET_PATH : "0.0.0.0",
+        SET_FLAGS_DEFAULT);
   }
   // Set the env variable to empty (in case the caller passed a value for it).
   unsetenv(kCuttlefishConfigEnvVarName);
 
-  return true;
+  return kernel_config;
 }
 
 std::string GetConfigFilePath(const CuttlefishConfig& config) {
diff --git a/host/commands/assemble_cvd/flags.h b/host/commands/assemble_cvd/flags.h
index 3c02d92..bd067ae 100644
--- a/host/commands/assemble_cvd/flags.h
+++ b/host/commands/assemble_cvd/flags.h
@@ -1,9 +1,13 @@
 #pragma once
 
+#include <fruit/fruit.h>
 #include <cstdint>
 #include <optional>
+#include <string>
+#include <vector>
 
 #include "common/libs/utils/environment.h"
+#include "common/libs/utils/result.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/fetcher_config.h"
 
@@ -14,12 +18,12 @@
   bool bootconfig_supported;
 };
 
-bool ParseCommandLineFlags(int* argc, char*** argv,
-                           KernelConfig* kernel_config);
+Result<KernelConfig> GetKernelConfigAndSetDefaults();
 // Must be called after ParseCommandLineFlags.
-CuttlefishConfig InitializeCuttlefishConfiguration(
-    const std::string& instance_dir, int modem_simulator_count,
-    KernelConfig kernel_config);
+CuttlefishConfig InitializeCuttlefishConfiguration(const std::string& root_dir,
+                                                   int modem_simulator_count,
+                                                   KernelConfig kernel_config,
+                                                   fruit::Injector<>& injector);
 
 std::string GetConfigFilePath(const CuttlefishConfig& config);
 std::string GetCuttlefishEnvPath();
diff --git a/host/commands/assemble_cvd/super_image_mixer.cc b/host/commands/assemble_cvd/super_image_mixer.cc
index c1e7eac..f048a81 100644
--- a/host/commands/assemble_cvd/super_image_mixer.cc
+++ b/host/commands/assemble_cvd/super_image_mixer.cc
@@ -55,14 +55,10 @@
 
 const std::string kMiscInfoPath = "META/misc_info.txt";
 const std::set<std::string> kDefaultTargetImages = {
-  "IMAGES/boot.img",
-  "IMAGES/odm.img",
-  "IMAGES/odm_dlkm.img",
-  "IMAGES/recovery.img",
-  "IMAGES/userdata.img",
-  "IMAGES/vbmeta.img",
-  "IMAGES/vendor.img",
-  "IMAGES/vendor_dlkm.img",
+    "IMAGES/boot.img",        "IMAGES/init_boot.img", "IMAGES/odm.img",
+    "IMAGES/odm_dlkm.img",    "IMAGES/recovery.img",  "IMAGES/userdata.img",
+    "IMAGES/vbmeta.img",      "IMAGES/vendor.img",    "IMAGES/vendor_dlkm.img",
+    "IMAGES/system_dlkm.img",
 };
 const std::set<std::string> kDefaultTargetBuildProp = {
   "ODM/build.prop",
@@ -133,7 +129,8 @@
   auto output_misc = default_misc;
   auto system_super_partitions = SuperPartitionComponents(system_misc);
   // Ensure specific skipped partitions end up in the misc_info.txt
-  for (auto partition : {"odm", "odm_dlkm", "vendor", "vendor_dlkm"}) {
+  for (auto partition :
+       {"odm", "odm_dlkm", "vendor", "vendor_dlkm", "system_dlkm"}) {
     if (std::find(system_super_partitions.begin(), system_super_partitions.end(),
                   partition) == system_super_partitions.end()) {
       system_super_partitions.push_back(partition);
@@ -241,10 +238,7 @@
   }) == 0;
 }
 
-} // namespace
-
-bool SuperImageNeedsRebuilding(const FetcherConfig& fetcher_config,
-                               const CuttlefishConfig&) {
+bool SuperImageNeedsRebuilding(const FetcherConfig& fetcher_config) {
   bool has_default_build = false;
   bool has_system_build = false;
   for (const auto& file_iter : fetcher_config.get_cvd_files()) {
@@ -288,4 +282,50 @@
   return success;
 }
 
+class SuperImageOutputPathTag {};
+
+class SuperImageRebuilderImpl : public SuperImageRebuilder {
+ public:
+  INJECT(SuperImageRebuilderImpl(const FetcherConfig& fetcher_config,
+                                 const CuttlefishConfig& config,
+                                 ANNOTATED(SuperImageOutputPathTag, std::string)
+                                     output_path))
+      : fetcher_config_(fetcher_config),
+        config_(config),
+        output_path_(output_path) {}
+
+  std::string Name() const override { return "SuperImageRebuilderImpl"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override {
+    if (SuperImageNeedsRebuilding(fetcher_config_)) {
+      bool success = RebuildSuperImage(fetcher_config_, config_, output_path_);
+      if (!success) {
+        LOG(ERROR)
+            << "Super image rebuilding requested but could not be completed.";
+        return false;
+      }
+    }
+    return true;
+  }
+
+  const FetcherConfig& fetcher_config_;
+  const CuttlefishConfig& config_;
+  std::string output_path_;
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<const FetcherConfig, const CuttlefishConfig>,
+                 SuperImageRebuilder>
+SuperImageRebuilderComponent(const std::string* output_path) {
+  return fruit::createComponent()
+      .bindInstance<fruit::Annotated<SuperImageOutputPathTag, std::string>>(
+          *output_path)
+      .bind<SuperImageRebuilder, SuperImageRebuilderImpl>()
+      .addMultibinding<SetupFeature, SuperImageRebuilder>();
+}
+
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/super_image_mixer.h b/host/commands/assemble_cvd/super_image_mixer.h
index 91f2c13..fda7f4d 100644
--- a/host/commands/assemble_cvd/super_image_mixer.h
+++ b/host/commands/assemble_cvd/super_image_mixer.h
@@ -13,15 +13,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <fruit/fruit.h>
+
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
 #include "host/libs/config/fetcher_config.h"
 
 namespace cuttlefish {
 
-bool SuperImageNeedsRebuilding(const FetcherConfig& fetcher_config,
-                               const CuttlefishConfig& config);
-bool RebuildSuperImage(const FetcherConfig& fetcher_config,
-                       const CuttlefishConfig& config,
-                       const std::string& output_path);
+class SuperImageRebuilder : public SetupFeature {};
+
+fruit::Component<fruit::Required<const FetcherConfig, const CuttlefishConfig>,
+                 SuperImageRebuilder>
+SuperImageRebuilderComponent(const std::string* output_path);
 
 } // namespace cuttlefish
diff --git a/host/commands/bt_connector/Android.bp b/host/commands/bt_connector/Android.bp
index d617eaa..8703a76 100644
--- a/host/commands/bt_connector/Android.bp
+++ b/host/commands/bt_connector/Android.bp
@@ -24,6 +24,7 @@
         "main.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libjsoncpp",
diff --git a/host/commands/bt_connector/OWNERS b/host/commands/bt_connector/OWNERS
index e8a4a00..e791d83 100644
--- a/host/commands/bt_connector/OWNERS
+++ b/host/commands/bt_connector/OWNERS
@@ -1,2 +1,3 @@
+include device/google/cuttlefish:/OWNERS
 include platform/system/bt:/OWNERS
 jeongik@google.com
\ No newline at end of file
diff --git a/host/commands/bt_connector/main.cpp b/host/commands/bt_connector/main.cpp
index fc17b31..a625768 100644
--- a/host/commands/bt_connector/main.cpp
+++ b/host/commands/bt_connector/main.cpp
@@ -14,7 +14,7 @@
  */
 
 #include <fcntl.h>
-#include <sys/poll.h>
+#include <poll.h>
 #include <unistd.h>
 #include <ios>
 #include <mutex>
diff --git a/host/commands/config_server/Android.bp b/host/commands/config_server/Android.bp
index b567bd9..51f3015 100644
--- a/host/commands/config_server/Android.bp
+++ b/host/commands/config_server/Android.bp
@@ -23,6 +23,7 @@
         "main.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libjsoncpp",
diff --git a/host/commands/console_forwarder/Android.bp b/host/commands/console_forwarder/Android.bp
index 230a0f7..c98c7aa 100644
--- a/host/commands/console_forwarder/Android.bp
+++ b/host/commands/console_forwarder/Android.bp
@@ -23,6 +23,7 @@
         "main.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libjsoncpp",
         "libcuttlefish_fs",
diff --git a/host/commands/console_forwarder/main.cpp b/host/commands/console_forwarder/main.cpp
index df5c9f7..bf8b21a 100644
--- a/host/commands/console_forwarder/main.cpp
+++ b/host/commands/console_forwarder/main.cpp
@@ -52,11 +52,13 @@
 class ConsoleForwarder {
  public:
   ConsoleForwarder(std::string console_path, SharedFD console_in,
-                   SharedFD console_out, SharedFD console_log)
+                   SharedFD console_out, SharedFD console_log,
+                   SharedFD kernel_log)
       : console_path_(console_path),
         console_in_(console_in),
         console_out_(console_out),
-        console_log_(console_log) {}
+        console_log_(console_log),
+        kernel_log_(kernel_log) {}
   [[noreturn]] void StartServer() {
     // Create a new thread to handle writes to the console
     writer_thread_ = std::thread([this]() { WriteLoop(); });
@@ -176,6 +178,7 @@
         if (client_fd->IsOpen()) {
           EnqueueWrite(buf_ptr, client_fd);
         }
+        EnqueueWrite(buf_ptr, kernel_log_);
       }
       if (read_set.IsSet(client_fd)) {
         std::shared_ptr<std::vector<char>> buf_ptr = std::make_shared<std::vector<char>>(4096);
@@ -199,6 +202,7 @@
   SharedFD console_in_;
   SharedFD console_out_;
   SharedFD console_log_;
+  SharedFD kernel_log_;
   std::thread writer_thread_;
   std::mutex write_queue_mutex_;
   std::condition_variable condvar_;
@@ -232,7 +236,10 @@
   auto console_log = instance.PerInstancePath("console_log");
   auto console_log_fd =
       SharedFD::Open(console_log.c_str(), O_CREAT | O_APPEND | O_WRONLY, 0666);
-  ConsoleForwarder console_forwarder(console_path, console_in, console_out, console_log_fd);
+  auto kernel_log_fd = SharedFD::Open(instance.kernel_log_pipe_name(),
+                                      O_APPEND | O_WRONLY, 0666);
+  ConsoleForwarder console_forwarder(console_path, console_in, console_out,
+                                     console_log_fd, kernel_log_fd);
 
   // Don't get a SIGPIPE from the clients
   CHECK(sigaction(SIGPIPE, nullptr, nullptr) == 0)
diff --git a/host/commands/cvd/Android.bp b/host/commands/cvd/Android.bp
new file mode 100644
index 0000000..c79447b
--- /dev/null
+++ b/host/commands/cvd/Android.bp
@@ -0,0 +1,81 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary_host {
+    name: "cvd",
+    symlinks: ["acloud"],
+    srcs: [
+        "acloud_command.cpp",
+        "command_sequence.cpp",
+        "epoll_loop.cpp",
+        "instance_lock.cpp",
+        "instance_manager.cpp",
+        "main.cc",
+        "scope_guard.cpp",
+        "server.cc",
+        "server_client.cpp",
+        "server_command.cpp",
+        "server_shutdown.cpp",
+        "server_version.cpp",
+    ],
+    target: {
+        host: {
+            stl: "libc++_static",
+            static_libs: [
+                "libbase",
+                "libcuttlefish_fs",
+                "libcuttlefish_utils",
+                "libext2_blkid",
+                "libfruit",
+                "libjsoncpp",
+                "liblog",
+                "libprotobuf-cpp-lite",
+                "libz",
+            ],
+        },
+        android: {
+            shared_libs: [
+                "libbase",
+                "libcuttlefish_fs",
+                "libcuttlefish_utils",
+                "libext2_blkid",
+                "libfruit",
+                "libjsoncpp",
+                "liblog",
+                "libprotobuf-cpp-lite",
+                "libz",
+            ],
+        },
+    },
+    static_libs: [
+        "libbuildversion",
+        "libcuttlefish_cvd_proto",
+        "libcuttlefish_host_config",
+    ],
+    required: [
+        "cvd_internal_host_bugreport",
+        "cvd_internal_start",
+        "cvd_internal_status",
+        "cvd_internal_stop",
+    ],
+    defaults: [
+        "cuttlefish_host",
+    ],
+    use_version_lib: true,
+}
diff --git a/host/commands/cvd/acloud_command.cpp b/host/commands/cvd/acloud_command.cpp
new file mode 100644
index 0000000..cc53d54
--- /dev/null
+++ b/host/commands/cvd/acloud_command.cpp
@@ -0,0 +1,335 @@
+/*
+ * 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.
+ */
+#include "host/commands/cvd/server.h"
+
+#include <optional>
+#include <vector>
+
+#include <android-base/strings.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/cvd/command_sequence.h"
+#include "host/commands/cvd/instance_lock.h"
+#include "host/commands/cvd/server_client.h"
+
+namespace cuttlefish {
+
+namespace {
+
+struct ConvertedAcloudCreateCommand {
+  InstanceLockFile lock;
+  std::vector<RequestWithStdio> requests;
+};
+
+/**
+ * Split a string into arguments based on shell tokenization rules.
+ *
+ * This behaves like `shlex.split` from python where arguments are separated
+ * based on whitespace, but quoting and quote escaping is respected. This
+ * function effectively removes one level of quoting from its inputs while
+ * making the split.
+ */
+Result<std::vector<std::string>> BashTokenize(const std::string& str) {
+  Command command("bash");
+  command.AddParameter("-c");
+  command.AddParameter("printf '%s\n' ", str);
+  std::string stdout;
+  std::string stderr;
+  auto ret = RunWithManagedStdio(std::move(command), nullptr, &stdout, &stderr);
+  CF_EXPECT(ret == 0, "printf fail \"" << stdout << "\", \"" << stderr << "\"");
+  return android::base::Split(stdout, "\n");
+}
+
+class ConvertAcloudCreateCommand {
+ public:
+  INJECT(ConvertAcloudCreateCommand(InstanceLockFileManager& lock_file_manager))
+      : lock_file_manager_(lock_file_manager) {}
+
+  Result<ConvertedAcloudCreateCommand> Convert(
+      const RequestWithStdio& request) {
+    auto arguments = ParseInvocation(request.Message()).arguments;
+    CF_EXPECT(arguments.size() > 0);
+    CF_EXPECT(arguments[0] == "create");
+    arguments.erase(arguments.begin());
+
+    const auto& request_command = request.Message().command_request();
+
+    std::vector<Flag> flags;
+    bool local_instance_set;
+    std::optional<int> local_instance;
+    auto local_instance_flag = Flag();
+    local_instance_flag.Alias(
+        {FlagAliasMode::kFlagConsumesArbitrary, "--local-instance"});
+    local_instance_flag.Setter([&local_instance_set,
+                                &local_instance](const FlagMatch& m) {
+      local_instance_set = true;
+      if (m.value != "" && local_instance) {
+        LOG(ERROR) << "Instance number already set, was \"" << *local_instance
+                   << "\", now set to \"" << m.value << "\"";
+        return false;
+      } else if (m.value != "" && !local_instance) {
+        local_instance = std::stoi(m.value);
+      }
+      return true;
+    });
+    flags.emplace_back(local_instance_flag);
+
+    bool verbose = false;
+    flags.emplace_back(Flag()
+                           .Alias({FlagAliasMode::kFlagExact, "-v"})
+                           .Alias({FlagAliasMode::kFlagExact, "-vv"})
+                           .Alias({FlagAliasMode::kFlagExact, "--verbose"})
+                           .Setter([&verbose](const FlagMatch&) {
+                             verbose = true;
+                             return true;
+                           }));
+
+    std::optional<std::string> branch;
+    flags.emplace_back(
+        Flag()
+            .Alias({FlagAliasMode::kFlagConsumesFollowing, "--branch"})
+            .Setter([&branch](const FlagMatch& m) {
+              branch = m.value;
+              return true;
+            }));
+
+    bool local_image;
+    flags.emplace_back(
+        Flag()
+            .Alias({FlagAliasMode::kFlagConsumesArbitrary, "--local-image"})
+            .Setter([&local_image](const FlagMatch& m) {
+              local_image = true;
+              return m.value == "";
+            }));
+
+    std::optional<std::string> build_id;
+    flags.emplace_back(
+        Flag()
+            .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build-id"})
+            .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build_id"})
+            .Setter([&build_id](const FlagMatch& m) {
+              build_id = m.value;
+              return true;
+            }));
+
+    std::optional<std::string> build_target;
+    flags.emplace_back(
+        Flag()
+            .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build-target"})
+            .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build_target"})
+            .Setter([&build_target](const FlagMatch& m) {
+              build_target = m.value;
+              return true;
+            }));
+
+    std::optional<std::string> launch_args;
+    flags.emplace_back(
+        Flag()
+            .Alias({FlagAliasMode::kFlagConsumesFollowing, "--launch-args"})
+            .Setter([&launch_args](const FlagMatch& m) {
+              launch_args = m.value;
+              return true;
+            }));
+
+    CF_EXPECT(ParseFlags(flags, arguments));
+    CF_EXPECT(arguments.size() == 0,
+              "Unrecognized arguments:'"
+                  << android::base::Join(arguments, "', '") << "'");
+
+    CF_EXPECT(local_instance_set == true,
+              "Only '--local-instance' is supported");
+    std::optional<InstanceLockFile> lock;
+    if (local_instance.has_value()) {
+      // TODO(schuffelen): Block here if it can be interruptible
+      lock = CF_EXPECT(lock_file_manager_.TryAcquireLock(*local_instance));
+    } else {
+      lock = CF_EXPECT(lock_file_manager_.TryAcquireUnusedLock());
+    }
+    CF_EXPECT(lock.has_value(), "Could not acquire instance lock");
+    CF_EXPECT(CF_EXPECT(lock->Status()) == InUseState::kNotInUse);
+
+    auto dir = TempDir() + "/acloud_cvd_temp/local-instance-" +
+               std::to_string(lock->Instance());
+
+    static constexpr char kAndroidHostOut[] = "ANDROID_HOST_OUT";
+
+    auto host_artifacts_path = request_command.env().find(kAndroidHostOut);
+    CF_EXPECT(host_artifacts_path != request_command.env().end(),
+              "Missing " << kAndroidHostOut);
+
+    std::vector<cvd::Request> request_protos;
+    if (local_image) {
+      cvd::Request& mkdir_request = request_protos.emplace_back();
+      auto& mkdir_command = *mkdir_request.mutable_command_request();
+      mkdir_command.add_args("cvd");
+      mkdir_command.add_args("mkdir");
+      mkdir_command.add_args("-p");
+      mkdir_command.add_args(dir);
+      auto& mkdir_env = *mkdir_command.mutable_env();
+      mkdir_env[kAndroidHostOut] = host_artifacts_path->second;
+      *mkdir_command.mutable_working_directory() = dir;
+    } else {
+      cvd::Request& fetch_request = request_protos.emplace_back();
+      auto& fetch_command = *fetch_request.mutable_command_request();
+      fetch_command.add_args("cvd");
+      fetch_command.add_args("fetch");
+      fetch_command.add_args("--directory");
+      fetch_command.add_args(dir);
+      if (branch || build_id || build_target) {
+        fetch_command.add_args("--default_build");
+        auto target = build_target ? "/" + *build_target : "";
+        auto build = build_id.value_or(branch.value_or("aosp-master"));
+        fetch_command.add_args(build + target);
+      }
+      *fetch_command.mutable_working_directory() = dir;
+      auto& fetch_env = *fetch_command.mutable_env();
+      fetch_env[kAndroidHostOut] = host_artifacts_path->second;
+    }
+
+    cvd::Request& start_request = request_protos.emplace_back();
+    auto& start_command = *start_request.mutable_command_request();
+    start_command.add_args("cvd");
+    start_command.add_args("start");
+    start_command.add_args("--daemon");
+    start_command.add_args("--undefok");
+    start_command.add_args("report_anonymous_usage_stats");
+    start_command.add_args("--report_anonymous_usage_stats");
+    start_command.add_args("y");
+    if (launch_args) {
+      for (const auto& arg : CF_EXPECT(BashTokenize(*launch_args))) {
+        start_command.add_args(arg);
+      }
+    }
+    static constexpr char kAndroidProductOut[] = "ANDROID_PRODUCT_OUT";
+    auto& start_env = *start_command.mutable_env();
+    if (local_image) {
+      start_env[kAndroidHostOut] = host_artifacts_path->second;
+
+      auto product_out = request_command.env().find(kAndroidProductOut);
+      CF_EXPECT(product_out != request_command.env().end(),
+                "Missing " << kAndroidProductOut);
+      start_env[kAndroidProductOut] = product_out->second;
+    } else {
+      start_env[kAndroidHostOut] = dir;
+      start_env[kAndroidProductOut] = dir;
+    }
+    start_env["CUTTLEFISH_INSTANCE"] = std::to_string(lock->Instance());
+    start_env["HOME"] = dir;
+    *start_command.mutable_working_directory() = dir;
+
+    std::vector<SharedFD> fds;
+    if (verbose) {
+      fds = request.FileDescriptors();
+    } else {
+      auto dev_null = SharedFD::Open("/dev/null", O_RDWR);
+      CF_EXPECT(dev_null->IsOpen(), dev_null->StrError());
+      fds = {dev_null, dev_null, dev_null};
+    }
+
+    ConvertedAcloudCreateCommand ret = {
+        .lock = {std::move(*lock)},
+    };
+    for (auto& request_proto : request_protos) {
+      ret.requests.emplace_back(request_proto, fds, request.Credentials());
+    }
+    return ret;
+  }
+
+ private:
+  InstanceLockFileManager& lock_file_manager_;
+};
+
+class TryAcloudCreateCommand : public CvdServerHandler {
+ public:
+  INJECT(TryAcloudCreateCommand(ConvertAcloudCreateCommand& converter))
+      : converter_(converter) {}
+  ~TryAcloudCreateCommand() = default;
+
+  Result<bool> CanHandle(const RequestWithStdio& request) const override {
+    auto invocation = ParseInvocation(request.Message());
+    return invocation.command == "try-acloud" &&
+           invocation.arguments.size() >= 1 &&
+           invocation.arguments[0] == "create";
+  }
+  Result<cvd::Response> Handle(const RequestWithStdio& request) override {
+    CF_EXPECT(converter_.Convert(request));
+    return CF_ERR("Unreleased");
+  }
+  Result<void> Interrupt() override { return CF_ERR("Can't be interrupted."); }
+
+ private:
+  ConvertAcloudCreateCommand& converter_;
+};
+
+class AcloudCreateCommand : public CvdServerHandler {
+ public:
+  INJECT(AcloudCreateCommand(CommandSequenceExecutor& executor,
+                             ConvertAcloudCreateCommand& converter))
+      : executor_(executor), converter_(converter) {}
+  ~AcloudCreateCommand() = default;
+
+  Result<bool> CanHandle(const RequestWithStdio& request) const override {
+    auto invocation = ParseInvocation(request.Message());
+    return invocation.command == "acloud" && invocation.arguments.size() >= 1 &&
+           invocation.arguments[0] == "create";
+  }
+  Result<cvd::Response> Handle(const RequestWithStdio& request) override {
+    std::unique_lock interrupt_lock(interrupt_mutex_);
+    if (interrupted_) {
+      return CF_ERR("Interrupted");
+    }
+    CF_EXPECT(CanHandle(request));
+
+    auto converted = CF_EXPECT(converter_.Convert(request));
+    interrupt_lock.unlock();
+    CF_EXPECT(executor_.Execute(converted.requests, request.Err()));
+
+    CF_EXPECT(converted.lock.Status(InUseState::kInUse));
+
+    cvd::Response response;
+    response.mutable_command_response();
+    return response;
+  }
+  Result<void> Interrupt() override {
+    std::scoped_lock interrupt_lock(interrupt_mutex_);
+    interrupted_ = true;
+    CF_EXPECT(executor_.Interrupt());
+    return {};
+  }
+
+ private:
+  CommandSequenceExecutor& executor_;
+  ConvertAcloudCreateCommand& converter_;
+
+  std::mutex interrupt_mutex_;
+  bool interrupted_ = false;
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<CvdCommandHandler>> AcloudCommandComponent() {
+  return fruit::createComponent()
+      .addMultibinding<CvdServerHandler, AcloudCreateCommand>()
+      .addMultibinding<CvdServerHandler, TryAcloudCreateCommand>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/command_sequence.cpp b/host/commands/cvd/command_sequence.cpp
new file mode 100644
index 0000000..21b146c
--- /dev/null
+++ b/host/commands/cvd/command_sequence.cpp
@@ -0,0 +1,102 @@
+//
+// 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.
+
+#include "host/commands/cvd/command_sequence.h"
+
+#include <fruit/fruit.h>
+
+#include "common/libs/fs/shared_buf.h"
+#include "host/commands/cvd/server.h"
+#include "host/commands/cvd/server_client.h"
+
+namespace cuttlefish {
+namespace {
+
+std::string BashEscape(const std::string& input) {
+  bool safe = true;
+  for (const auto& c : input) {
+    if ('0' <= c && c <= '9') {
+      continue;
+    }
+    if ('a' <= c && c <= 'z') {
+      continue;
+    }
+    if ('A' <= c && c <= 'Z') {
+      continue;
+    }
+    if (c == '_' || c == '-' || c == '.' || c == ',' || c == '/') {
+      continue;
+    }
+    safe = false;
+  }
+  using android::base::StringReplace;
+  return safe ? input : "'" + StringReplace(input, "'", "\\'", true) + "'";
+}
+
+std::string FormattedCommand(const cvd::CommandRequest command) {
+  std::stringstream effective_command;
+  effective_command << "Executing `";
+  for (const auto& [name, val] : command.env()) {
+    effective_command << BashEscape(name) << "=" << BashEscape(val) << " ";
+  }
+  for (const auto& argument : command.args()) {
+    effective_command << BashEscape(argument) << " ";
+  }
+  effective_command.seekp(-1, effective_command.cur);
+  effective_command << "`\n";  // Overwrite last space
+  return effective_command.str();
+}
+
+}  // namespace
+
+CommandSequenceExecutor::CommandSequenceExecutor(
+    CvdCommandHandler& inner_handler)
+    : inner_handler_(inner_handler) {}
+
+Result<void> CommandSequenceExecutor::Interrupt() {
+  CF_EXPECT(inner_handler_.Interrupt());
+  return {};
+}
+
+Result<void> CommandSequenceExecutor::Execute(
+    const std::vector<RequestWithStdio>& requests, SharedFD report) {
+  std::unique_lock interrupt_lock(interrupt_mutex_);
+  if (interrupted_) {
+    return CF_ERR("Interrupted");
+  }
+  for (const auto& request : requests) {
+    auto& inner_proto = request.Message();
+    CF_EXPECT(inner_proto.has_command_request());
+    auto& command = inner_proto.command_request();
+    std::string str = FormattedCommand(command);
+    CF_EXPECT(WriteAll(report, str) == str.size(), report->StrError());
+
+    interrupt_lock.unlock();
+    auto response = CF_EXPECT(inner_handler_.Handle(request));
+    interrupt_lock.lock();
+    if (interrupted_) {
+      return CF_ERR("Interrupted");
+    }
+    CF_EXPECT(response.status().code() == cvd::Status::OK,
+              "Reason: \"" << response.status().message() << "\"");
+
+    static const char kDoneMsg[] = "Done\n";
+    CF_EXPECT(WriteAll(request.Err(), kDoneMsg) == sizeof(kDoneMsg) - 1,
+              request.Err()->StrError());
+  }
+  return {};
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/command_sequence.h b/host/commands/cvd/command_sequence.h
new file mode 100644
index 0000000..bea305b
--- /dev/null
+++ b/host/commands/cvd/command_sequence.h
@@ -0,0 +1,41 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <vector>
+
+#include <fruit/fruit.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/commands/cvd/server.h"
+#include "host/commands/cvd/server_client.h"
+
+namespace cuttlefish {
+
+class CommandSequenceExecutor {
+ public:
+  INJECT(CommandSequenceExecutor(CvdCommandHandler& inner_handler));
+
+  Result<void> Interrupt();
+  Result<void> Execute(const std::vector<RequestWithStdio>&, SharedFD report);
+
+ private:
+  std::mutex interrupt_mutex_;
+  bool interrupted_ = false;
+  CvdCommandHandler& inner_handler_;
+};
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/epoll_loop.cpp b/host/commands/cvd/epoll_loop.cpp
new file mode 100644
index 0000000..51a6f6a
--- /dev/null
+++ b/host/commands/cvd/epoll_loop.cpp
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+#include "host/commands/cvd/epoll_loop.h"
+
+#include <android-base/errors.h>
+
+#include "common/libs/fs/epoll.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+EpollPool::EpollPool(Epoll epoll) : epoll_(std::move(epoll)) {}
+
+EpollPool::EpollPool(EpollPool&& other) {
+  std::unique_lock own_lock(instance_mutex_, std::defer_lock);
+  std::unique_lock other_lock(other.instance_mutex_, std::defer_lock);
+  std::unique_lock own_cb_lock(callbacks_mutex_, std::defer_lock);
+  std::unique_lock other_cb_lock(other.callbacks_mutex_, std::defer_lock);
+  std::lock(own_lock, other_lock, own_cb_lock, other_cb_lock);
+  epoll_ = std::move(other.epoll_);
+  callbacks_ = std::move(other.callbacks_);
+}
+
+EpollPool& EpollPool::operator=(EpollPool&& other) {
+  std::unique_lock own_lock(instance_mutex_, std::defer_lock);
+  std::unique_lock other_lock(other.instance_mutex_, std::defer_lock);
+  std::unique_lock own_cb_lock(callbacks_mutex_, std::defer_lock);
+  std::unique_lock other_cb_lock(other.callbacks_mutex_, std::defer_lock);
+  std::lock(own_lock, other_lock, own_cb_lock, other_cb_lock);
+  epoll_ = std::move(other.epoll_);
+  callbacks_ = std::move(other.callbacks_);
+
+  return *this;
+}
+
+Result<void> EpollPool::Register(SharedFD fd, uint32_t events,
+                                 EpollCallback callback) {
+  std::shared_lock instance_lock(instance_mutex_, std::defer_lock);
+  std::unique_lock callbacks_lock(callbacks_mutex_, std::defer_lock);
+  std::lock(instance_lock, callbacks_lock);
+  if (callbacks_.find(fd) != callbacks_.end()) {
+    return CF_ERR("Already have a callback created");
+  }
+  CF_EXPECT(epoll_.AddOrModify(fd, events | EPOLLONESHOT));
+  callbacks_[fd] = std::move(callback);
+  return {};
+}
+
+Result<void> EpollPool::HandleEvent() {
+  auto event = CF_EXPECT(epoll_.Wait());
+  if (!event) {
+    return {};
+  }
+  EpollCallback callback;
+  {
+    std::lock_guard lock(callbacks_mutex_);
+    auto it = callbacks_.find(event->fd);
+    CF_EXPECT(it != callbacks_.end(), "Could not find event callback");
+    callback = std::move(it->second);
+    callbacks_.erase(it);
+  }
+  CF_EXPECT(callback(*event));
+  return {};
+}
+
+Result<void> EpollPool::Remove(SharedFD fd) {
+  std::shared_lock instance_lock(instance_mutex_, std::defer_lock);
+  std::unique_lock callbacks_lock(callbacks_mutex_, std::defer_lock);
+  std::lock(instance_lock, callbacks_lock);
+  CF_EXPECT(epoll_.Delete(fd), "No callback registered with epoll");
+  callbacks_.erase(fd);
+  return {};
+}
+
+fruit::Component<EpollPool> EpollLoopComponent() {
+  return fruit::createComponent()
+      .registerProvider([]() -> EpollPool {
+        return EpollPool(OR_FATAL(Epoll::Create()));
+      });
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/epoll_loop.h b/host/commands/cvd/epoll_loop.h
new file mode 100644
index 0000000..8ae48d4
--- /dev/null
+++ b/host/commands/cvd/epoll_loop.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#include <functional>
+#include <map>
+#include <mutex>
+
+#include <fruit/fruit.h>
+
+#include "common/libs/fs/epoll.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+#pragma once
+
+namespace cuttlefish {
+
+using EpollCallback = std::function<Result<void>(EpollEvent)>;
+
+class EpollPool {
+ public:
+  EpollPool(Epoll);
+  EpollPool(EpollPool&&);
+  EpollPool& operator=(EpollPool&&);
+
+  /**
+   * The `callback` function will be invoked with an EpollEvent containing `fd`
+   * and a subset of the bits in `events` matching which events were actually
+   * observed. The callback is invoked exactly once (enforced via EPOLLONESHOT)
+   * and must be re-`Register`ed to receive events again. This can be done
+   * in the callback implementation. Callbacks are invoked by callers of the
+   * `HandleEvent` function, and any errors produced by the callback function
+   * will manifest there. Callbacks that return errors will not be automatically
+   * re-registered.
+   */
+  Result<void> Register(SharedFD fd, uint32_t events, EpollCallback callback);
+  Result<void> HandleEvent();
+  Result<void> Remove(SharedFD fd);
+
+ private:
+  std::shared_mutex instance_mutex_;
+  Epoll epoll_;
+  std::mutex callbacks_mutex_;
+  std::map<SharedFD, EpollCallback> callbacks_;
+};
+
+fruit::Component<EpollPool> EpollLoopComponent();
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/instance_lock.cpp b/host/commands/cvd/instance_lock.cpp
new file mode 100644
index 0000000..de8b7b5
--- /dev/null
+++ b/host/commands/cvd/instance_lock.cpp
@@ -0,0 +1,188 @@
+/*
+ * 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.
+ */
+
+#include "host/commands/cvd/instance_lock.h"
+
+#include <sys/file.h>
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <fruit/fruit.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/environment.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+InstanceLockFile::InstanceLockFile(SharedFD fd, int instance_num)
+    : fd_(fd), instance_num_(instance_num) {}
+
+int InstanceLockFile::Instance() const { return instance_num_; }
+
+Result<InUseState> InstanceLockFile::Status() const {
+  CF_EXPECT(fd_->LSeek(0, SEEK_SET) == 0, fd_->StrError());
+  char state_char = static_cast<char>(InUseState::kNotInUse);
+  CF_EXPECT(fd_->Read(&state_char, 1) >= 0, fd_->StrError());
+  switch (state_char) {
+    case static_cast<char>(InUseState::kInUse):
+      return InUseState::kInUse;
+    case static_cast<char>(InUseState::kNotInUse):
+      return InUseState::kNotInUse;
+    default:
+      return CF_ERR("Unexpected state value \"" << state_char << "\"");
+  }
+}
+
+Result<void> InstanceLockFile::Status(InUseState state) {
+  CF_EXPECT(fd_->LSeek(0, SEEK_SET) == 0, fd_->StrError());
+  char state_char = static_cast<char>(state);
+  CF_EXPECT(fd_->Write(&state_char, 1) == 1, fd_->StrError());
+  return {};
+}
+
+bool InstanceLockFile::operator<(const InstanceLockFile& other) const {
+  if (instance_num_ != other.instance_num_) {
+    return instance_num_ < other.instance_num_;
+  }
+  return fd_ < other.fd_;
+}
+
+InstanceLockFileManager::InstanceLockFileManager() = default;
+
+// Replicates tempfile.gettempdir() in Python
+std::string TempDir() {
+  std::vector<std::string> try_dirs = {
+      StringFromEnv("TMPDIR", ""),
+      StringFromEnv("TEMP", ""),
+      StringFromEnv("TMP", ""),
+      "/tmp",
+      "/var/tmp",
+      "/usr/tmp",
+  };
+  for (const auto& try_dir : try_dirs) {
+    if (DirectoryExists(try_dir)) {
+      return try_dir;
+    }
+  }
+  return CurrentDirectory();
+}
+
+static Result<SharedFD> OpenLockFile(int instance_num) {
+  std::stringstream path;
+  path << TempDir() << "/acloud_cvd_temp/";
+  CF_EXPECT(EnsureDirectoryExists(path.str()));
+  path << "local-instance-" << instance_num << ".lock";
+  auto fd = SharedFD::Open(path.str(), O_CREAT | O_RDWR, 0666);
+  CF_EXPECT(fd->IsOpen(), "open(\"" << path.str() << "\"): " << fd->StrError());
+  return fd;
+}
+
+Result<InstanceLockFile> InstanceLockFileManager::AcquireLock(
+    int instance_num) {
+  auto fd = CF_EXPECT(OpenLockFile(instance_num));
+  CF_EXPECT(fd->Flock(LOCK_EX), fd->StrError());
+  return InstanceLockFile(fd, instance_num);
+}
+
+Result<std::set<InstanceLockFile>> InstanceLockFileManager::AcquireLocks(
+    const std::set<int>& instance_nums) {
+  std::set<InstanceLockFile> locks;
+  for (const auto& num : instance_nums) {
+    locks.emplace(CF_EXPECT(AcquireLock(num)));
+  }
+  return locks;
+}
+
+Result<std::optional<InstanceLockFile>> InstanceLockFileManager::TryAcquireLock(
+    int instance_num) {
+  auto fd = CF_EXPECT(OpenLockFile(instance_num));
+  int flock_result = fd->Flock(LOCK_EX | LOCK_NB);
+  if (flock_result == 0) {
+    return InstanceLockFile(fd, instance_num);
+  } else if (flock_result == -1 && fd->GetErrno() == EWOULDBLOCK) {
+    return {};
+  }
+  return CF_ERR("flock " << instance_num << " failed: " << fd->StrError());
+}
+
+Result<std::set<InstanceLockFile>> InstanceLockFileManager::TryAcquireLocks(
+    const std::set<int>& instance_nums) {
+  std::set<InstanceLockFile> locks;
+  for (const auto& num : instance_nums) {
+    auto lock = CF_EXPECT(TryAcquireLock(num));
+    if (lock) {
+      locks.emplace(std::move(*lock));
+    }
+  }
+  return locks;
+}
+
+static Result<std::set<int>> AllInstanceNums() {
+  // Estimate this by looking at available tap devices
+  // clang-format off
+  /** Sample format:
+Inter-|   Receive                                                |  Transmit
+ face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
+cvd-wtap-02:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
+  */
+  // clang-format on
+  static constexpr char kPath[] = "/proc/net/dev";
+  std::string proc_net_dev;
+  using android::base::ReadFileToString;
+  CF_EXPECT(ReadFileToString(kPath, &proc_net_dev, /* follow_symlinks */ true));
+  auto lines = android::base::Split(proc_net_dev, "\n");
+  std::set<int> etaps, mtaps, wtaps;
+  for (const auto& line : lines) {
+    std::set<int>* tap_set = nullptr;
+    if (android::base::StartsWith(line, "cvd-etap-")) {
+      tap_set = &etaps;
+    } else if (android::base::StartsWith(line, "cvd-mtap-")) {
+      tap_set = &mtaps;
+    } else if (android::base::StartsWith(line, "cvd-wtap-")) {
+      tap_set = &wtaps;
+    } else {
+      continue;
+    }
+    tap_set->insert(std::stoi(line.substr(std::string{"cvd-etap-"}.size())));
+  }
+  std::set<int> emtaps;
+  std::set_intersection(etaps.begin(), etaps.end(), mtaps.begin(), mtaps.end(),
+                        std::inserter(emtaps, emtaps.begin()));
+  std::set<int> emwtaps;
+  std::set_intersection(emtaps.begin(), emtaps.end(), wtaps.begin(),
+                        wtaps.end(), std::inserter(emwtaps, emwtaps.begin()));
+  return emwtaps;
+}
+
+Result<std::optional<InstanceLockFile>>
+InstanceLockFileManager::TryAcquireUnusedLock() {
+  auto nums = CF_EXPECT(AllInstanceNums());
+  for (const auto& num : nums) {
+    auto lock = CF_EXPECT(TryAcquireLock(num));
+    if (lock && CF_EXPECT(lock->Status()) == InUseState::kNotInUse) {
+      return std::move(*lock);
+    }
+  }
+  return {};
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/instance_lock.h b/host/commands/cvd/instance_lock.h
new file mode 100644
index 0000000..5d3deea
--- /dev/null
+++ b/host/commands/cvd/instance_lock.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <set>
+
+#include <fruit/fruit.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+class InstanceLockFileManager;
+
+enum class InUseState : char {
+  kInUse = 'I',
+  kNotInUse = 'N',
+};
+
+// Replicates tempfile.gettempdir() in Python
+std::string TempDir();
+
+// This class is not thread safe.
+class InstanceLockFile {
+ public:
+  int Instance() const;
+  Result<InUseState> Status() const;
+  Result<void> Status(InUseState);
+
+  bool operator<(const InstanceLockFile&) const;
+
+ private:
+  friend class InstanceLockFileManager;
+
+  InstanceLockFile(SharedFD fd, int instance_num);
+
+  SharedFD fd_;
+  int instance_num_;
+};
+
+class InstanceLockFileManager {
+ public:
+  INJECT(InstanceLockFileManager());
+
+  Result<InstanceLockFile> AcquireLock(int instance_num);
+  Result<std::set<InstanceLockFile>> AcquireLocks(const std::set<int>& nums);
+
+  Result<std::optional<InstanceLockFile>> TryAcquireLock(int instance_num);
+  Result<std::set<InstanceLockFile>> TryAcquireLocks(const std::set<int>& nums);
+
+  // Best-effort attempt to find a free instance id.
+  Result<std::optional<InstanceLockFile>> TryAcquireUnusedLock();
+};
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/instance_manager.cpp b/host/commands/cvd/instance_manager.cpp
new file mode 100644
index 0000000..7ea1b74
--- /dev/null
+++ b/host/commands/cvd/instance_manager.cpp
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+
+#include "host/commands/cvd/instance_manager.h"
+
+#include <map>
+#include <mutex>
+#include <optional>
+#include <thread>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/cvd/server_constants.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
+
+namespace cuttlefish {
+
+std::optional<std::string> GetCuttlefishConfigPath(const std::string& home) {
+  std::string home_realpath;
+  if (DirectoryExists(home)) {
+    CHECK(android::base::Realpath(home, &home_realpath));
+    static const char kSuffix[] = "/cuttlefish_assembly/cuttlefish_config.json";
+    std::string config_path = AbsolutePath(home_realpath + kSuffix);
+    if (FileExists(config_path)) {
+      return config_path;
+    }
+  }
+  return {};
+}
+
+InstanceManager::InstanceManager(InstanceLockFileManager& lock_manager)
+    : lock_manager_(lock_manager) {}
+
+bool InstanceManager::HasInstanceGroups() const {
+  std::lock_guard lock(instance_groups_mutex_);
+  return !instance_groups_.empty();
+}
+
+void InstanceManager::SetInstanceGroup(
+    const InstanceManager::InstanceGroupDir& dir,
+    const InstanceManager::InstanceGroupInfo& info) {
+  std::lock_guard assemblies_lock(instance_groups_mutex_);
+  instance_groups_[dir] = info;
+}
+
+void InstanceManager::RemoveInstanceGroup(
+    const InstanceManager::InstanceGroupDir& dir) {
+  std::lock_guard assemblies_lock(instance_groups_mutex_);
+  instance_groups_.erase(dir);
+}
+
+Result<InstanceManager::InstanceGroupInfo> InstanceManager::GetInstanceGroup(
+    const InstanceManager::InstanceGroupDir& dir) const {
+  std::lock_guard assemblies_lock(instance_groups_mutex_);
+  auto info_it = instance_groups_.find(dir);
+  if (info_it == instance_groups_.end()) {
+    return CF_ERR("No group dir \"" << dir << "\"");
+  } else {
+    return info_it->second;
+  }
+}
+
+cvd::Status InstanceManager::CvdFleet(const SharedFD& out,
+                                      const std::string& env_config) const {
+  std::lock_guard assemblies_lock(instance_groups_mutex_);
+  const char _GroupDeviceInfoStart[] = "[\n";
+  const char _GroupDeviceInfoSeparate[] = ",\n";
+  const char _GroupDeviceInfoEnd[] = "]\n";
+  WriteAll(out, _GroupDeviceInfoStart);
+  for (const auto& [group_dir, group_info] : instance_groups_) {
+    auto config_path = GetCuttlefishConfigPath(group_dir);
+    if (FileExists(env_config)) {
+      config_path = env_config;
+    }
+    if (config_path) {
+      // Reads CuttlefishConfig::instance_names(), which must remain stable
+      // across changes to config file format (within server_constants.h major
+      // version).
+      auto config = CuttlefishConfig::GetFromFile(*config_path);
+      if (config) {
+        Command command(group_info.host_binaries_dir + kStatusBin);
+        command.AddParameter("--print");
+        command.AddParameter("--all_instances");
+        command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, out);
+        command.AddEnvironmentVariable(kCuttlefishConfigEnvVarName,
+                                       *config_path);
+        if (int wait_result = command.Start().Wait(); wait_result != 0) {
+          WriteAll(out, "      (unknown instance status error)");
+        }
+      }
+    }
+    if (group_dir != instance_groups_.rbegin()->first) {
+      WriteAll(out, _GroupDeviceInfoSeparate);
+    }
+  }
+  WriteAll(out, _GroupDeviceInfoEnd);
+  cvd::Status status;
+  status.set_code(cvd::Status::OK);
+  return status;
+}
+
+cvd::Status InstanceManager::CvdClear(const SharedFD& out,
+                                      const SharedFD& err) {
+  std::lock_guard lock(instance_groups_mutex_);
+  cvd::Status status;
+  for (const auto& [group_dir, group_info] : instance_groups_) {
+    auto config_path = GetCuttlefishConfigPath(group_dir);
+    if (config_path) {
+      // Stop all instances that are using this group dir.
+      Command command(group_info.host_binaries_dir + kStopBin);
+      // Delete the instance dirs.
+      command.AddParameter("--clear_instance_dirs");
+      command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, out);
+      command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, err);
+      command.AddEnvironmentVariable(kCuttlefishConfigEnvVarName, *config_path);
+      if (int wait_result = command.Start().Wait(); wait_result != 0) {
+        WriteAll(
+            out,
+            "Warning: error stopping instances for dir \"" + group_dir +
+                "\".\nThis can happen if instances are already stopped.\n");
+      }
+      for (const auto& instance : group_info.instances) {
+        auto lock = lock_manager_.TryAcquireLock(instance);
+        if (lock.ok() && (*lock)) {
+          (*lock)->Status(InUseState::kNotInUse);
+        }
+      }
+    }
+  }
+  RemoveFile(StringFromEnv("HOME", ".") + "/cuttlefish_runtime");
+  RemoveFile(GetGlobalConfigFileLink());
+  WriteAll(out, "Stopped all known instances\n");
+
+  instance_groups_.clear();
+  status.set_code(cvd::Status::OK);
+  return status;
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/instance_manager.h b/host/commands/cvd/instance_manager.h
new file mode 100644
index 0000000..6aebbdb
--- /dev/null
+++ b/host/commands/cvd/instance_manager.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <mutex>
+#include <optional>
+#include <string>
+
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+#include "host/commands/cvd/instance_lock.h"
+
+namespace cuttlefish {
+
+constexpr char kStatusBin[] = "cvd_internal_status";
+constexpr char kStopBin[] = "cvd_internal_stop";
+
+class InstanceManager {
+ public:
+  using InstanceGroupDir = std::string;
+  struct InstanceGroupInfo {
+    std::string host_binaries_dir;
+    std::set<int> instances;
+  };
+
+  INJECT(InstanceManager(InstanceLockFileManager&));
+
+  bool HasInstanceGroups() const;
+  void SetInstanceGroup(const InstanceGroupDir&, const InstanceGroupInfo&);
+  void RemoveInstanceGroup(const InstanceGroupDir&);
+  Result<InstanceGroupInfo> GetInstanceGroup(const InstanceGroupDir&) const;
+
+  cvd::Status CvdClear(const SharedFD& out, const SharedFD& err);
+  cvd::Status CvdFleet(const SharedFD& out, const std::string& envconfig) const;
+
+ private:
+  InstanceLockFileManager& lock_manager_;
+
+  mutable std::mutex instance_groups_mutex_;
+  std::map<InstanceGroupDir, InstanceGroupInfo> instance_groups_;
+};
+
+std::optional<std::string> GetCuttlefishConfigPath(
+    const std::string& assembly_dir);
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/main.cc b/host/commands/cvd/main.cc
new file mode 100644
index 0000000..4e77852
--- /dev/null
+++ b/host/commands/cvd/main.cc
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <chrono>
+#include <iostream>
+#include <map>
+#include <optional>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <build/version.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/environment.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/shared_fd_flag.h"
+#include "common/libs/utils/subprocess.h"
+#include "common/libs/utils/unix_sockets.h"
+#include "host/commands/cvd/server.h"
+#include "host/commands/cvd/server_constants.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/host_tools_version.h"
+
+namespace cuttlefish {
+namespace {
+
+Result<SharedFD> ConnectToServer() {
+  auto connection =
+      SharedFD::SocketLocalClient(cvd::kServerSocketPath,
+                                  /*is_abstract=*/true, SOCK_SEQPACKET);
+  if (!connection->IsOpen()) {
+    auto connection =
+        SharedFD::SocketLocalClient(cvd::kServerSocketPath,
+                                    /*is_abstract=*/true, SOCK_STREAM);
+  }
+  if (!connection->IsOpen()) {
+    return CF_ERR("Failed to connect to server" << connection->StrError());
+  }
+  return connection;
+}
+
+class CvdClient {
+ public:
+  Result<void> EnsureCvdServerRunning(const std::string& host_tool_directory,
+                                      int num_retries = 1) {
+    cvd::Request request;
+    request.mutable_version_request();
+    auto response = SendRequest(request);
+
+    // If cvd_server is not running, start and wait before checking its version.
+    if (!response.ok()) {
+      CF_EXPECT(StartCvdServer(host_tool_directory));
+      response = CF_EXPECT(SendRequest(request));
+    }
+    CF_EXPECT(CheckStatus(response->status(), "GetVersion"));
+    CF_EXPECT(response->has_version_response(),
+              "GetVersion call missing VersionResponse.");
+
+    auto server_version = response->version_response().version();
+    if (server_version.major() != cvd::kVersionMajor) {
+      return CF_ERR("Major version difference: cvd("
+                    << cvd::kVersionMajor << "." << cvd::kVersionMinor
+                    << ") != cvd_server(" << server_version.major() << "."
+                    << server_version.minor()
+                    << "). Try `cvd kill-server` or `pkill cvd_server`.");
+    }
+    if (server_version.minor() < cvd::kVersionMinor) {
+      std::cerr << "Minor version of cvd_server is older than latest. "
+                << "Attempting to restart..." << std::endl;
+      CF_EXPECT(StopCvdServer(/*clear=*/false));
+      CF_EXPECT(StartCvdServer(host_tool_directory));
+      if (num_retries > 0) {
+        CF_EXPECT(EnsureCvdServerRunning(host_tool_directory, num_retries - 1));
+        return {};
+      } else {
+        return CF_ERR("Unable to start the cvd_server with version "
+                      << cvd::kVersionMajor << "." << cvd::kVersionMinor);
+      }
+    }
+    if (server_version.build() != android::build::GetBuildNumber()) {
+      LOG(VERBOSE) << "cvd_server client version ("
+                   << android::build::GetBuildNumber()
+                   << ") does not match server version ("
+                   << server_version.build() << std::endl;
+    }
+    auto self_crc32 = FileCrc("/proc/self/exe");
+    if (server_version.crc32() != self_crc32) {
+      LOG(VERBOSE) << "cvd_server client checksum (" << self_crc32
+                   << ") doesn't match server checksum ("
+                   << server_version.crc32() << std::endl;
+    }
+    return {};
+  }
+
+  Result<void> StopCvdServer(bool clear) {
+    if (!server_) {
+      // server_ may not represent a valid connection even while the server is
+      // running, if we haven't tried to connect. This establishes first whether
+      // the server is running.
+      auto connection_attempt = ConnectToServer();
+      if (!connection_attempt.ok()) {
+        return {};
+      }
+    }
+
+    cvd::Request request;
+    auto shutdown_request = request.mutable_shutdown_request();
+    if (clear) {
+      shutdown_request->set_clear(true);
+    }
+
+    // Send the server a pipe with the Shutdown request that it
+    // will close when it fully exits.
+    SharedFD read_pipe, write_pipe;
+    CF_EXPECT(cuttlefish::SharedFD::Pipe(&read_pipe, &write_pipe),
+              "Unable to create shutdown pipe: " << strerror(errno));
+
+    auto response = SendRequest(request, /*extra_fd=*/write_pipe);
+
+    // If the server is already not running then SendRequest will fail.
+    // We treat this as success.
+    if (!response.ok()) {
+      server_.reset();
+      return {};
+    }
+
+    CF_EXPECT(CheckStatus(response->status(), "Shutdown"));
+    CF_EXPECT(response->has_shutdown_response(),
+              "Shutdown call missing ShutdownResponse.");
+
+    // Clear out the server_ socket.
+    server_.reset();
+
+    // Close the write end of the pipe in this process. Now the only
+    // process that may have the write end still open is the cvd_server.
+    write_pipe->Close();
+
+    // Wait for the pipe to close by attempting to read from the pipe.
+    char buf[1];  // Any size >0 should work for read attempt.
+    CF_EXPECT(read_pipe->Read(buf, sizeof(buf)) <= 0,
+              "Unexpected read value from cvd_server shutdown pipe.");
+    return {};
+  }
+
+  Result<void> HandleCommand(std::vector<std::string> args,
+                             std::vector<std::string> env) {
+    cvd::Request request;
+    auto command_request = request.mutable_command_request();
+    for (const std::string& arg : args) {
+      command_request->add_args(arg);
+    }
+    for (const std::string& e : env) {
+      auto eq_pos = e.find('=');
+      if (eq_pos == std::string::npos) {
+        LOG(WARNING) << "Environment var in unknown format: " << e;
+        continue;
+      }
+      (*command_request->mutable_env())[e.substr(0, eq_pos)] =
+          e.substr(eq_pos + 1);
+    }
+    std::unique_ptr<char, void(*)(void*)> cwd(getcwd(nullptr, 0), &free);
+    command_request->set_working_directory(cwd.get());
+    command_request->set_wait_behavior(cvd::WAIT_BEHAVIOR_COMPLETE);
+
+    auto response = CF_EXPECT(SendRequest(request));
+    CF_EXPECT(CheckStatus(response.status(), "HandleCommand"));
+    CF_EXPECT(response.has_command_response(),
+              "HandleCommand call missing CommandResponse.");
+    return {};
+  }
+
+ private:
+  std::optional<UnixMessageSocket> server_;
+
+  Result<void> SetServer(const SharedFD& server) {
+    CF_EXPECT(!server_, "Already have a server");
+    CF_EXPECT(server->IsOpen(), server->StrError());
+    server_ = UnixMessageSocket(server);
+    CF_EXPECT(server_->EnableCredentials(true).ok(),
+              "Unable to enable UnixMessageSocket credentials.");
+    return {};
+  }
+
+  Result<cvd::Response> SendRequest(const cvd::Request& request,
+                                    std::optional<SharedFD> extra_fd = {}) {
+    if (!server_) {
+      CF_EXPECT(SetServer(CF_EXPECT(ConnectToServer())));
+    }
+    // Serialize and send the request.
+    std::string serialized;
+    CF_EXPECT(request.SerializeToString(&serialized),
+              "Unable to serialize request proto.");
+    UnixSocketMessage request_message;
+
+    std::vector<SharedFD> control_fds = {
+        SharedFD::Dup(0),
+        SharedFD::Dup(1),
+        SharedFD::Dup(2),
+    };
+    if (extra_fd) {
+      control_fds.push_back(*extra_fd);
+    }
+    auto control = CF_EXPECT(ControlMessage::FromFileDescriptors(control_fds));
+    request_message.control.emplace_back(std::move(control));
+
+    request_message.data =
+        std::vector<char>(serialized.begin(), serialized.end());
+    CF_EXPECT(server_->WriteMessage(request_message));
+
+    // Read and parse the response.
+    auto read_result = CF_EXPECT(server_->ReadMessage());
+    serialized = std::string(read_result.data.begin(), read_result.data.end());
+    cvd::Response response;
+    CF_EXPECT(response.ParseFromString(serialized),
+              "Unable to parse serialized response proto.");
+    return response;
+  }
+
+  Result<void> StartCvdServer(const std::string& host_tool_directory) {
+    SharedFD server_fd =
+        SharedFD::SocketLocalServer(cvd::kServerSocketPath,
+                                    /*is_abstract=*/true, SOCK_SEQPACKET, 0666);
+    CF_EXPECT(server_fd->IsOpen(), server_fd->StrError());
+
+    // TODO(b/196114111): Investigate fully "daemonizing" the cvd_server.
+    CF_EXPECT(setenv("ANDROID_HOST_OUT", host_tool_directory.c_str(),
+                     /*overwrite=*/true) == 0);
+    Command command("/proc/self/exe");
+    command.AddParameter("-INTERNAL_server_fd=", server_fd);
+    SubprocessOptions options;
+    options.ExitWithParent(false);
+    command.Start(options);
+
+    // Connect to the server_fd, which waits for startup.
+    CF_EXPECT(SetServer(SharedFD::SocketLocalClient(cvd::kServerSocketPath,
+                                                    /*is_abstract=*/true,
+                                                    SOCK_SEQPACKET)));
+    return {};
+  }
+
+  Result<void> CheckStatus(const cvd::Status& status, const std::string& rpc) {
+    if (status.code() == cvd::Status::OK) {
+      return {};
+    }
+    return CF_ERR("Received error response for \"" << rpc << "\":\n"
+                                                   << status.message()
+                                                   << "\nIn client");
+  }
+};
+
+[[noreturn]] void CallPythonAcloud(std::vector<std::string>& args) {
+  auto android_top = StringFromEnv("ANDROID_BUILD_TOP", "");
+  if (android_top == "") {
+    LOG(FATAL) << "Could not find android environment. Please run "
+               << "\"source build/envsetup.sh\".";
+    abort();
+  }
+  // TODO(b/206893146): Detect what the platform actually is.
+  auto py_acloud_path =
+      android_top + "/prebuilts/asuite/acloud/linux-x86/acloud";
+  char** new_argv = new char*[args.size() + 1];
+  for (size_t i = 0; i < args.size(); i++) {
+    new_argv[i] = args[i].data();
+  }
+  new_argv[args.size()] = nullptr;
+  execv(py_acloud_path.data(), new_argv);
+  PLOG(FATAL) << "execv(" << py_acloud_path << ", ...) failed";
+  abort();
+}
+
+Result<int> CvdMain(int argc, char** argv, char** envp) {
+  android::base::InitLogging(argv, android::base::StderrLogger);
+
+  std::vector<std::string> args = ArgsToVec(argc, argv);
+  std::vector<Flag> flags;
+
+  CvdClient client;
+
+  // TODO(b/206893146): Make this decision inside the server.
+  if (args[0] == "acloud") {
+    auto server_running = client.EnsureCvdServerRunning(
+        android::base::Dirname(android::base::GetExecutableDirectory()));
+    if (server_running.ok()) {
+      // TODO(schuffelen): Deduplicate when calls to setenv are removed.
+      std::vector<std::string> env;
+      for (char** e = envp; *e != 0; e++) {
+        env.emplace_back(*e);
+      }
+      args[0] = "try-acloud";
+      auto attempt = client.HandleCommand(args, env);
+      if (attempt.ok()) {
+        args[0] = "acloud";
+        CF_EXPECT(client.HandleCommand(args, env));
+        return 0;
+      } else {
+        CallPythonAcloud(args);
+      }
+    } else {
+      // Something is wrong with the server, fall back to python acloud
+      CallPythonAcloud(args);
+    }
+  }
+  bool clean = false;
+  flags.emplace_back(GflagsCompatFlag("clean", clean));
+  SharedFD internal_server_fd;
+  flags.emplace_back(SharedFDFlag("INTERNAL_server_fd", internal_server_fd));
+
+  CF_EXPECT(ParseFlags(flags, args));
+
+  if (internal_server_fd->IsOpen()) {
+    return CF_EXPECT(CvdServerMain(internal_server_fd));
+  } else if (argv[0] == std::string("/proc/self/exe")) {
+    return CF_ERR(
+        "Expected to be in server mode, but didn't get a server "
+        "fd: "
+        << internal_server_fd->StrError());
+  }
+
+  // Special case for `cvd kill-server`, handled by directly
+  // stopping the cvd_server.
+  if (argc > 1 && strcmp("kill-server", argv[1]) == 0) {
+    CF_EXPECT(client.StopCvdServer(/*clear=*/true));
+    return 0;
+  }
+
+  // Special case for --clean flag, used to clear any existing state.
+  if (clean) {
+    LOG(INFO) << "cvd invoked with --clean; "
+              << "stopping the cvd_server before continuing.";
+    CF_EXPECT(client.StopCvdServer(/*clear=*/true));
+  }
+
+  // Handle all remaining commands by forwarding them to the cvd_server.
+  CF_EXPECT(client.EnsureCvdServerRunning(android::base::Dirname(
+                android::base::GetExecutableDirectory())),
+            "Unable to ensure cvd_server is running.");
+
+  // TODO(schuffelen): Deduplicate when calls to setenv are removed.
+  std::vector<std::string> env;
+  for (char** e = envp; *e != 0; e++) {
+    env.emplace_back(*e);
+  }
+  CF_EXPECT(client.HandleCommand(args, env));
+  return 0;
+}
+
+}  // namespace
+}  // namespace cuttlefish
+
+int main(int argc, char** argv, char** envp) {
+  auto result = cuttlefish::CvdMain(argc, argv, envp);
+  if (result.ok()) {
+    return *result;
+  } else {
+    std::cerr << result.error() << std::endl;
+    return -1;
+  }
+}
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/cvd/proto/Android.bp
similarity index 60%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/cvd/proto/Android.bp
index 9f25445..75de449 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/cvd/proto/Android.bp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-#include "common/libs/utils/size_utils.h"
-
-#include <unistd.h>
-
-namespace cuttlefish {
-
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-}  // namespace cuttlefish
+cc_library_static {
+    name: "libcuttlefish_cvd_proto",
+    host_supported: true,
+    proto: {
+        export_proto_headers: true,
+        type: "lite",
+        //include_dirs: ["external/protobuf/src"],
+    },
+    srcs: ["cvd_server.proto"],
+}
diff --git a/host/commands/cvd/proto/cvd_server.proto b/host/commands/cvd/proto/cvd_server.proto
new file mode 100644
index 0000000..903f03e
--- /dev/null
+++ b/host/commands/cvd/proto/cvd_server.proto
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package cuttlefish.cvd;
+
+message Status {
+  // Subset of status codes from gRPC.
+  enum Code {
+    OK = 0;
+    FAILED_PRECONDITION = 9;
+    INTERNAL = 13;
+  }
+
+  Code code = 1;
+  string message = 2;
+}
+
+message Request {
+  oneof contents {
+    // Returns the version of the CvdServer.
+    VersionRequest version_request = 1;
+    // Requests the CvdServer to shutdown.
+    ShutdownRequest shutdown_request = 2;
+    // Requests the CvdServer to execute a command on behalf of the client.
+    CommandRequest command_request = 3;
+  }
+}
+
+message Response {
+  Status status = 1;
+  oneof contents {
+    VersionResponse version_response = 2;
+    ShutdownResponse shutdown_response = 3;
+    CommandResponse command_response = 4;
+  }
+}
+
+message Version {
+  int32 major = 1;
+  int32 minor = 2;
+  string build = 3;
+  uint32 crc32 = 4;
+}
+
+message VersionRequest {}
+message VersionResponse {
+  Version version = 1;
+}
+
+message ShutdownRequest {
+  // If true, clears instance and assembly state before shutting down.
+  bool clear = 1;
+}
+message ShutdownResponse {}
+
+enum WaitBehavior {
+  WAIT_BEHAVIOR_UNKNOWN = 0;
+  WAIT_BEHAVIOR_START = 1;
+  WAIT_BEHAVIOR_COMPLETE = 2;
+}
+
+message CommandRequest {
+  // The args that should be executed, including the subcommand.
+  repeated string args = 1;
+  // Environment variables that will be used by the subcommand.
+  map<string, string> env = 2;
+  string working_directory = 3;
+  WaitBehavior wait_behavior = 4;
+}
+message CommandResponse {}
diff --git a/host/commands/cvd/scope_guard.cpp b/host/commands/cvd/scope_guard.cpp
new file mode 100644
index 0000000..a0e8bbb
--- /dev/null
+++ b/host/commands/cvd/scope_guard.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+#include "host/commands/cvd/scope_guard.h"
+
+#include <functional>
+
+namespace cuttlefish {
+
+ScopeGuard::ScopeGuard() = default;
+
+ScopeGuard::ScopeGuard(std::function<void()> fn) : fn_(fn) {}
+
+ScopeGuard::ScopeGuard(ScopeGuard&&) = default;
+
+ScopeGuard& ScopeGuard::operator=(ScopeGuard&&) = default;
+
+ScopeGuard::~ScopeGuard() {
+  if (fn_) {
+    fn_();
+  }
+}
+
+void ScopeGuard::Cancel() { fn_ = nullptr; }
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/cvd/scope_guard.h
similarity index 64%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/cvd/scope_guard.h
index 9f25445..596bc66 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/cvd/scope_guard.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -13,16 +13,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#pragma once
 
-#include "common/libs/utils/size_utils.h"
-
-#include <unistd.h>
+#include <functional>
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+class ScopeGuard {
+ public:
+  ScopeGuard();
+  explicit ScopeGuard(std::function<void()> fn);
+  ScopeGuard(ScopeGuard&&);
+  ~ScopeGuard();
+  ScopeGuard& operator=(ScopeGuard&&);
+
+  void Cancel();
+
+ private:
+  std::function<void()> fn_;
+};
 
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/server.cc b/host/commands/cvd/server.cc
new file mode 100644
index 0000000..6e74ddf
--- /dev/null
+++ b/host/commands/cvd/server.cc
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/cvd/server.h"
+
+#include <signal.h>
+
+#include <atomic>
+#include <future>
+#include <map>
+#include <mutex>
+#include <optional>
+#include <thread>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/shared_fd_flag.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/cvd/epoll_loop.h"
+#include "host/commands/cvd/scope_guard.h"
+#include "host/commands/cvd/server_constants.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
+
+namespace cuttlefish {
+
+static fruit::Component<> RequestComponent(CvdServer* server,
+                                           InstanceManager* instance_manager) {
+  return fruit::createComponent()
+      .bindInstance(*server)
+      .bindInstance(*instance_manager)
+      .install(AcloudCommandComponent)
+      .install(cvdCommandComponent)
+      .install(cvdShutdownComponent)
+      .install(cvdVersionComponent);
+}
+
+static constexpr int kNumThreads = 10;
+
+CvdServer::CvdServer(EpollPool& epoll_pool, InstanceManager& instance_manager)
+    : epoll_pool_(epoll_pool),
+      instance_manager_(instance_manager),
+      running_(true) {
+  std::scoped_lock lock(threads_mutex_);
+  for (auto i = 0; i < kNumThreads; i++) {
+    threads_.emplace_back([this]() {
+      while (running_) {
+        auto result = epoll_pool_.HandleEvent();
+        if (!result.ok()) {
+          LOG(ERROR) << "Epoll worker error:\n" << result.error();
+        }
+      }
+      auto wakeup = BestEffortWakeup();
+      CHECK(wakeup.ok()) << wakeup.error().message();
+    });
+  }
+}
+
+CvdServer::~CvdServer() {
+  running_ = false;
+  auto wakeup = BestEffortWakeup();
+  CHECK(wakeup.ok()) << wakeup.error().message();
+  Join();
+}
+
+Result<void> CvdServer::BestEffortWakeup() {
+  // This attempts to cascade through the responder threads, forcing them
+  // to wake up and see that running_ is false, then exit and wake up
+  // further threads.
+  auto eventfd = SharedFD::Event();
+  CF_EXPECT(eventfd->IsOpen(), eventfd->StrError());
+  CF_EXPECT(eventfd->EventfdWrite(1) == 0, eventfd->StrError());
+
+  auto cb = [](EpollEvent) -> Result<void> { return {}; };
+  CF_EXPECT(epoll_pool_.Register(eventfd, EPOLLIN, cb));
+  return {};
+}
+
+void CvdServer::Stop() {
+  {
+    std::lock_guard lock(ongoing_requests_mutex_);
+    running_ = false;
+  }
+  while (true) {
+    std::shared_ptr<OngoingRequest> request;
+    {
+      std::lock_guard lock(ongoing_requests_mutex_);
+      if (ongoing_requests_.empty()) {
+        break;
+      }
+      auto it = ongoing_requests_.begin();
+      request = *it;
+      ongoing_requests_.erase(it);
+    }
+    {
+      std::lock_guard lock(request->mutex);
+      if (request->handler == nullptr) {
+        continue;
+      }
+      request->handler->Interrupt();
+    }
+    std::scoped_lock lock(threads_mutex_);
+    for (auto& thread : threads_) {
+      auto current_thread = thread.get_id() == std::this_thread::get_id();
+      auto matching_thread = thread.get_id() == request->thread_id;
+      if (!current_thread && matching_thread && thread.joinable()) {
+        thread.join();
+      }
+    }
+  }
+}
+
+void CvdServer::Join() {
+  for (auto& thread : threads_) {
+    if (thread.joinable()) {
+      thread.join();
+    }
+  }
+}
+
+static Result<CvdServerHandler*> RequestHandler(
+    const RequestWithStdio& request,
+    const std::vector<CvdServerHandler*>& handlers) {
+  Result<cvd::Response> response;
+  std::vector<CvdServerHandler*> compatible_handlers;
+  for (auto& handler : handlers) {
+    if (CF_EXPECT(handler->CanHandle(request))) {
+      compatible_handlers.push_back(handler);
+    }
+  }
+  CF_EXPECT(compatible_handlers.size() == 1,
+            "Expected exactly one handler for message, found "
+                << compatible_handlers.size());
+  return compatible_handlers[0];
+}
+
+Result<void> CvdServer::StartServer(SharedFD server_fd) {
+  auto cb = [this](EpollEvent ev) -> Result<void> {
+    CF_EXPECT(AcceptClient(ev));
+    return {};
+  };
+  CF_EXPECT(epoll_pool_.Register(server_fd, EPOLLIN, cb));
+  return {};
+}
+
+Result<void> CvdServer::AcceptClient(EpollEvent event) {
+  ScopeGuard stop_on_failure([this] { Stop(); });
+
+  CF_EXPECT(event.events & EPOLLIN);
+  auto client_fd = SharedFD::Accept(*event.fd);
+  CF_EXPECT(client_fd->IsOpen(), client_fd->StrError());
+  auto client_cb = [this](EpollEvent ev) -> Result<void> {
+    CF_EXPECT(HandleMessage(ev));
+    return {};
+  };
+  CF_EXPECT(epoll_pool_.Register(client_fd, EPOLLIN, client_cb));
+
+  auto self_cb = [this](EpollEvent ev) -> Result<void> {
+    CF_EXPECT(AcceptClient(ev));
+    return {};
+  };
+  CF_EXPECT(epoll_pool_.Register(event.fd, EPOLLIN, self_cb));
+
+  stop_on_failure.Cancel();
+  return {};
+}
+
+Result<void> CvdServer::HandleMessage(EpollEvent event) {
+  ScopeGuard abandon_client([this, event] { epoll_pool_.Remove(event.fd); });
+
+  if (event.events & EPOLLHUP) {  // Client went away.
+    epoll_pool_.Remove(event.fd);
+    return {};
+  }
+
+  CF_EXPECT(event.events & EPOLLIN);
+  auto request = CF_EXPECT(GetRequest(event.fd));
+  if (!request) {  // End-of-file / client went away.
+    epoll_pool_.Remove(event.fd);
+    return {};
+  }
+
+  auto response = HandleRequest(*request, event.fd);
+  if (!response.ok()) {
+    cvd::Response failure_message;
+    failure_message.mutable_status()->set_code(cvd::Status::INTERNAL);
+    failure_message.mutable_status()->set_message(response.error().message());
+    CF_EXPECT(SendResponse(event.fd, failure_message));
+    return {};  // Error already sent to the client, don't repeat on the server
+  }
+  CF_EXPECT(SendResponse(event.fd, *response));
+
+  auto self_cb = [this](EpollEvent ev) -> Result<void> {
+    CF_EXPECT(HandleMessage(ev));
+    return {};
+  };
+  CF_EXPECT(epoll_pool_.Register(event.fd, EPOLLIN, self_cb));
+
+  abandon_client.Cancel();
+  return {};
+}
+
+Result<cvd::Response> CvdServer::HandleRequest(RequestWithStdio request,
+                                               SharedFD client) {
+  fruit::Injector<> injector(RequestComponent, this, &instance_manager_);
+  auto possible_handlers = injector.getMultibindings<CvdServerHandler>();
+
+  // Even if the interrupt callback outlives the request handler, it'll only
+  // hold on to this struct which will be cleaned out when the request handler
+  // exits.
+  auto shared = std::make_shared<OngoingRequest>();
+  shared->handler = CF_EXPECT(RequestHandler(request, possible_handlers));
+  shared->thread_id = std::this_thread::get_id();
+
+  {
+    std::lock_guard lock(ongoing_requests_mutex_);
+    if (running_) {
+      ongoing_requests_.insert(shared);
+    } else {
+      // We're executing concurrently with a Stop() call.
+      return {};
+    }
+  }
+  ScopeGuard remove_ongoing_request([this, shared] {
+    std::lock_guard lock(ongoing_requests_mutex_);
+    ongoing_requests_.erase(shared);
+  });
+
+  auto interrupt_cb = [shared](EpollEvent) -> Result<void> {
+    std::lock_guard lock(shared->mutex);
+    CF_EXPECT(shared->handler != nullptr);
+    CF_EXPECT(shared->handler->Interrupt());
+    return {};
+  };
+  CF_EXPECT(epoll_pool_.Register(client, EPOLLHUP, interrupt_cb));
+
+  auto response = CF_EXPECT(shared->handler->Handle(request));
+  {
+    std::lock_guard lock(shared->mutex);
+    shared->handler = nullptr;
+  }
+  CF_EXPECT(epoll_pool_.Remove(client));  // Delete interrupt handler
+
+  return response;
+}
+
+static fruit::Component<CvdServer> ServerComponent() {
+  return fruit::createComponent()
+      .install(EpollLoopComponent);
+}
+
+Result<int> CvdServerMain(SharedFD server_fd) {
+  LOG(INFO) << "Starting server";
+
+  signal(SIGPIPE, SIG_IGN);
+
+  CF_EXPECT(server_fd->IsOpen(), "Did not receive a valid cvd_server fd");
+
+  fruit::Injector<CvdServer> injector(ServerComponent);
+  CvdServer& server = injector.get<CvdServer&>();
+  server.StartServer(server_fd);
+  server.Join();
+
+  return 0;
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/server.h b/host/commands/cvd/server.h
new file mode 100644
index 0000000..8a77081
--- /dev/null
+++ b/host/commands/cvd/server.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <map>
+#include <optional>
+#include <shared_mutex>
+#include <string>
+#include <vector>
+
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/epoll.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/subprocess.h"
+#include "common/libs/utils/unix_sockets.h"
+#include "host/commands/cvd/epoll_loop.h"
+#include "host/commands/cvd/instance_manager.h"
+#include "host/commands/cvd/server_client.h"
+
+namespace cuttlefish {
+
+class CvdServerHandler {
+ public:
+  virtual ~CvdServerHandler() = default;
+
+  virtual Result<bool> CanHandle(const RequestWithStdio&) const = 0;
+  virtual Result<cvd::Response> Handle(const RequestWithStdio&) = 0;
+  virtual Result<void> Interrupt() = 0;
+};
+
+class CvdServer {
+ public:
+  INJECT(CvdServer(EpollPool&, InstanceManager&));
+  ~CvdServer();
+
+  Result<void> StartServer(SharedFD server);
+  void Stop();
+  void Join();
+
+ private:
+  struct OngoingRequest {
+    CvdServerHandler* handler;
+    std::mutex mutex;
+    std::thread::id thread_id;
+  };
+
+  Result<void> AcceptClient(EpollEvent);
+  Result<void> HandleMessage(EpollEvent);
+  Result<cvd::Response> HandleRequest(RequestWithStdio, SharedFD client);
+  Result<void> BestEffortWakeup();
+
+  EpollPool& epoll_pool_;
+  InstanceManager& instance_manager_;
+  std::atomic_bool running_ = true;
+
+  std::mutex ongoing_requests_mutex_;
+  std::set<std::shared_ptr<OngoingRequest>> ongoing_requests_;
+  // TODO(schuffelen): Move this thread pool to another class.
+  std::mutex threads_mutex_;
+  std::vector<std::thread> threads_;
+};
+
+class CvdCommandHandler : public CvdServerHandler {
+ public:
+  INJECT(CvdCommandHandler(InstanceManager& instance_manager));
+
+  Result<bool> CanHandle(const RequestWithStdio&) const override;
+  Result<cvd::Response> Handle(const RequestWithStdio&) override;
+  Result<void> Interrupt() override;
+
+ private:
+  InstanceManager& instance_manager_;
+  std::optional<Subprocess> subprocess_;
+  std::mutex interruptible_;
+  bool interrupted_ = false;
+};
+
+fruit::Component<fruit::Required<InstanceManager>> cvdCommandComponent();
+fruit::Component<fruit::Required<CvdServer, InstanceManager>>
+cvdShutdownComponent();
+fruit::Component<> cvdVersionComponent();
+fruit::Component<fruit::Required<CvdCommandHandler>> AcloudCommandComponent();
+
+struct CommandInvocation {
+  std::string command;
+  std::vector<std::string> arguments;
+};
+
+CommandInvocation ParseInvocation(const cvd::Request& request);
+
+Result<int> CvdServerMain(SharedFD server_fd);
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/server_client.cpp b/host/commands/cvd/server_client.cpp
new file mode 100644
index 0000000..017b2f5
--- /dev/null
+++ b/host/commands/cvd/server_client.cpp
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+#include "host/commands/cvd/server_client.h"
+
+#include <atomic>
+#include <condition_variable>
+#include <memory>
+#include <optional>
+#include <queue>
+#include <thread>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/unix_sockets.h"
+
+namespace cuttlefish {
+
+Result<UnixMessageSocket> GetClient(const SharedFD& client) {
+  UnixMessageSocket result(client);
+  CF_EXPECT(result.EnableCredentials(true),
+            "Unable to enable UnixMessageSocket credentials.");
+  return result;
+}
+
+Result<std::optional<RequestWithStdio>> GetRequest(const SharedFD& client) {
+  UnixMessageSocket reader =
+      CF_EXPECT(GetClient(client), "Couldn't get client");
+  auto read_result = CF_EXPECT(reader.ReadMessage(), "Couldn't read message");
+
+  if (read_result.data.empty()) {
+    LOG(VERBOSE) << "Read empty packet, so the client has probably closed the "
+                    "connection.";
+    return {};
+  };
+
+  std::string serialized(read_result.data.begin(), read_result.data.end());
+  cvd::Request request;
+  CF_EXPECT(request.ParseFromString(serialized),
+            "Unable to parse serialized request proto.");
+
+  CF_EXPECT(read_result.HasFileDescriptors(),
+            "Missing stdio fds from request.");
+  auto fds = CF_EXPECT(read_result.FileDescriptors(),
+                       "Error reading stdio fds from request");
+  CF_EXPECT(fds.size() == 3 || fds.size() == 4, "Wrong number of FDs, received "
+                                                    << fds.size()
+                                                    << ", wanted 3 or 4");
+
+  std::optional<ucred> creds;
+  if (read_result.HasCredentials()) {
+    // TODO(b/198453477): Use Credentials to control command access.
+    creds = CF_EXPECT(read_result.Credentials(), "Failed to get credentials");
+    LOG(DEBUG) << "Has credentials, uid=" << creds->uid;
+  }
+
+  return RequestWithStdio(std::move(request), std::move(fds), std::move(creds));
+}
+
+Result<void> SendResponse(const SharedFD& client,
+                          const cvd::Response& response) {
+  std::string serialized;
+  CF_EXPECT(response.SerializeToString(&serialized),
+            "Unable to serialize response proto.");
+  UnixSocketMessage message;
+  message.data = std::vector<char>(serialized.begin(), serialized.end());
+
+  UnixMessageSocket writer =
+      CF_EXPECT(GetClient(client), "Couldn't get client");
+  CF_EXPECT(writer.WriteMessage(message));
+  return {};
+}
+
+RequestWithStdio::RequestWithStdio(cvd::Request message,
+                                   std::vector<SharedFD> fds,
+                                   std::optional<ucred> creds)
+    : message_(message), fds_(std::move(fds)), creds_(creds) {}
+
+const cvd::Request& RequestWithStdio::Message() const { return message_; }
+
+const std::vector<SharedFD>& RequestWithStdio::FileDescriptors() const {
+  return fds_;
+}
+
+SharedFD RequestWithStdio::In() const {
+  return fds_.size() > 0 ? fds_[0] : SharedFD();
+}
+
+SharedFD RequestWithStdio::Out() const {
+  return fds_.size() > 1 ? fds_[1] : SharedFD();
+}
+
+SharedFD RequestWithStdio::Err() const {
+  return fds_.size() > 2 ? fds_[2] : SharedFD();
+}
+
+std::optional<SharedFD> RequestWithStdio::Extra() const {
+  return fds_.size() > 3 ? fds_[3] : std::optional<SharedFD>{};
+}
+
+std::optional<ucred> RequestWithStdio::Credentials() const { return creds_; }
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/server_client.h b/host/commands/cvd/server_client.h
new file mode 100644
index 0000000..751a890
--- /dev/null
+++ b/host/commands/cvd/server_client.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/socket.h>
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/unix_sockets.h"
+
+namespace cuttlefish {
+
+class RequestWithStdio {
+ public:
+  RequestWithStdio(cvd::Request, std::vector<SharedFD>, std::optional<ucred>);
+
+  const cvd::Request& Message() const;
+  const std::vector<SharedFD>& FileDescriptors() const;
+  SharedFD In() const;
+  SharedFD Out() const;
+  SharedFD Err() const;
+  std::optional<SharedFD> Extra() const;
+  std::optional<ucred> Credentials() const;
+
+ private:
+  cvd::Request message_;
+  std::vector<SharedFD> fds_;
+  std::optional<ucred> creds_;
+};
+
+Result<UnixMessageSocket> GetClient(const SharedFD& client);
+Result<std::optional<RequestWithStdio>> GetRequest(const SharedFD& client);
+Result<void> SendResponse(const SharedFD& client,
+                          const cvd::Response& response);
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/server_command.cpp b/host/commands/cvd/server_command.cpp
new file mode 100644
index 0000000..e7e9b4a
--- /dev/null
+++ b/host/commands/cvd/server_command.cpp
@@ -0,0 +1,338 @@
+/*
+ * 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.
+ */
+
+#include "host/commands/cvd/server.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/environment.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/cvd/instance_manager.h"
+#include "host/libs/config/cuttlefish_config.h"
+
+namespace cuttlefish {
+namespace {
+
+constexpr char kHostBugreportBin[] = "cvd_internal_host_bugreport";
+constexpr char kStartBin[] = "cvd_internal_start";
+constexpr char kFetchBin[] = "fetch_cvd";
+constexpr char kMkdirBin[] = "/bin/mkdir";
+
+constexpr char kClearBin[] = "clear_placeholder";  // Unused, runs CvdClear()
+constexpr char kFleetBin[] = "fleet_placeholder";  // Unused, runs CvdFleet()
+constexpr char kHelpBin[] = "help_placeholder";  // Unused, prints kHelpMessage.
+constexpr char kHelpMessage[] = R"(Cuttlefish Virtual Device (CVD) CLI.
+
+usage: cvd <command> <args>
+
+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.
+  status              Check and print the state of a running instance.
+  host_bugreport      Capture a host bugreport, including configs, logs, and tombstones.
+
+Args:
+  <command args>      Each command has its own set of args. See cvd help <command>.
+  --clean             If provided, runs cvd kill-server before the requested command.
+)";
+
+const std::map<std::string, std::string> CommandToBinaryMap = {
+    {"help", kHelpBin},
+    {"host_bugreport", kHostBugreportBin},
+    {"cvd_host_bugreport", kHostBugreportBin},
+    {"start", kStartBin},
+    {"launch_cvd", kStartBin},
+    {"status", kStatusBin},
+    {"cvd_status", kStatusBin},
+    {"stop", kStopBin},
+    {"stop_cvd", kStopBin},
+    {"clear", kClearBin},
+    {"fetch", kFetchBin},
+    {"fetch_cvd", kFetchBin},
+    {"mkdir", kMkdirBin},
+    {"fleet", kFleetBin}};
+
+}  // namespace
+
+CvdCommandHandler::CvdCommandHandler(InstanceManager& instance_manager)
+    : instance_manager_(instance_manager) {}
+
+Result<bool> CvdCommandHandler::CanHandle(
+    const RequestWithStdio& request) const {
+  auto invocation = ParseInvocation(request.Message());
+  return CommandToBinaryMap.find(invocation.command) !=
+         CommandToBinaryMap.end();
+}
+
+Result<cvd::Response> CvdCommandHandler::Handle(
+    const RequestWithStdio& request) {
+  std::unique_lock interrupt_lock(interruptible_);
+  if (interrupted_) {
+    return CF_ERR("Interrupted");
+  }
+  CF_EXPECT(CanHandle(request));
+  cvd::Response response;
+  response.mutable_command_response();
+
+  auto invocation = ParseInvocation(request.Message());
+
+  auto subcommand_bin = CommandToBinaryMap.find(invocation.command);
+  CF_EXPECT(subcommand_bin != CommandToBinaryMap.end());
+  auto bin = subcommand_bin->second;
+
+  // HOME is used to possibly set CuttlefishConfig path env variable later. This
+  // env variable is used by subcommands when locating the config.
+  auto request_home = request.Message().command_request().env().find("HOME");
+  std::string home =
+      request_home != request.Message().command_request().env().end()
+          ? request_home->second
+          : StringFromEnv("HOME", ".");
+
+  // Create a copy of args before parsing, to be passed to subcommands.
+  auto args = invocation.arguments;
+  auto args_copy = invocation.arguments;
+
+  auto host_artifacts_path =
+      request.Message().command_request().env().find("ANDROID_HOST_OUT");
+  if (host_artifacts_path == request.Message().command_request().env().end()) {
+    response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION);
+    response.mutable_status()->set_message(
+        "Missing ANDROID_HOST_OUT in client environment.");
+    return response;
+  }
+
+  if (bin == kHelpBin) {
+    // Handle `cvd help`
+    if (args.empty()) {
+      WriteAll(request.Out(), kHelpMessage);
+      response.mutable_status()->set_code(cvd::Status::OK);
+      return response;
+    }
+
+    // Certain commands have no detailed help text.
+    std::set<std::string> builtins = {"help", "clear", "kill-server"};
+    auto it = CommandToBinaryMap.find(args[0]);
+    if (it == CommandToBinaryMap.end() ||
+        builtins.find(args[0]) != builtins.end()) {
+      WriteAll(request.Out(), kHelpMessage);
+      response.mutable_status()->set_code(cvd::Status::OK);
+      return response;
+    }
+
+    // Handle `cvd help <subcommand>` by calling the subcommand with --help.
+    bin = it->second;
+    args_copy.push_back("--help");
+  } else if (bin == kClearBin) {
+    *response.mutable_status() =
+        instance_manager_.CvdClear(request.Out(), request.Err());
+    return response;
+  } else if (bin == kFleetBin) {
+    auto env_config = request.Message().command_request().env().find(
+        kCuttlefishConfigEnvVarName);
+    std::string config_path;
+    if (env_config != request.Message().command_request().env().end()) {
+      config_path = env_config->second;
+    }
+    *response.mutable_status() =
+        instance_manager_.CvdFleet(request.Out(), config_path);
+    return response;
+  } else if (bin == kStartBin) {
+    auto first_instance = 1;
+    auto instance_env =
+        request.Message().command_request().env().find("CUTTLEFISH_INSTANCE");
+    if (instance_env != request.Message().command_request().env().end()) {
+      first_instance = std::stoi(instance_env->second);
+    }
+    auto ins_flag = GflagsCompatFlag("base_instance_num", first_instance);
+    auto num_instances = 1;
+    auto num_instances_flag = GflagsCompatFlag("num_instances", num_instances);
+    CF_EXPECT(ParseFlags({ins_flag, num_instances_flag}, args));
+
+    // Track this assembly_dir in the fleet.
+    InstanceManager::InstanceGroupInfo info;
+    info.host_binaries_dir = host_artifacts_path->second + "/bin/";
+    for (int i = first_instance; i < first_instance + num_instances; i++) {
+      info.instances.insert(i);
+    }
+    instance_manager_.SetInstanceGroup(home, info);
+  }
+
+  Command command("(replaced)");
+  if (bin == kFetchBin) {
+    command.SetExecutable(HostBinaryPath("fetch_cvd"));
+  } else if (bin == kMkdirBin) {
+    command.SetExecutable(kMkdirBin);
+  } else {
+    auto assembly_info = CF_EXPECT(instance_manager_.GetInstanceGroup(home));
+    command.SetExecutable(assembly_info.host_binaries_dir + bin);
+  }
+  for (const std::string& arg : args_copy) {
+    command.AddParameter(arg);
+  }
+
+  // Set CuttlefishConfig path based on assembly dir,
+  // used by subcommands when locating the CuttlefishConfig.
+  if (request.Message().command_request().env().count(
+          kCuttlefishConfigEnvVarName) == 0) {
+    auto config_path = GetCuttlefishConfigPath(home);
+    if (config_path) {
+      command.AddEnvironmentVariable(kCuttlefishConfigEnvVarName, *config_path);
+    }
+  }
+  for (auto& it : request.Message().command_request().env()) {
+    command.UnsetFromEnvironment(it.first);
+    command.AddEnvironmentVariable(it.first, it.second);
+  }
+
+  // Redirect stdin, stdout, stderr back to the cvd client
+  command.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, request.In());
+  command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, request.Out());
+  command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, request.Err());
+  SubprocessOptions options;
+
+  if (request.Message().command_request().wait_behavior() ==
+      cvd::WAIT_BEHAVIOR_START) {
+    options.ExitWithParent(false);
+  }
+
+  const auto& working_dir =
+      request.Message().command_request().working_directory();
+  if (!working_dir.empty()) {
+    auto fd = SharedFD::Open(working_dir, O_RDONLY | O_PATH | O_DIRECTORY);
+    CF_EXPECT(fd->IsOpen(),
+              "Couldn't open \"" << working_dir << "\": " << fd->StrError());
+    command.SetWorkingDirectory(fd);
+  }
+
+  subprocess_ = command.Start(options);
+
+  if (request.Message().command_request().wait_behavior() ==
+      cvd::WAIT_BEHAVIOR_START) {
+    response.mutable_status()->set_code(cvd::Status::OK);
+    return response;
+  }
+  interrupt_lock.unlock();
+
+  siginfo_t infop{};
+
+  // This blocks until the process exits, but doesn't reap it.
+  auto result = subprocess_->Wait(&infop, WEXITED | WNOWAIT);
+  CF_EXPECT(result != -1, "Lost track of subprocess pid");
+  interrupt_lock.lock();
+  // Perform a reaping wait on the process (which should already have exited).
+  result = subprocess_->Wait(&infop, WEXITED);
+  CF_EXPECT(result != -1, "Lost track of subprocess pid");
+  // The double wait avoids a race around the kernel reusing pids. Waiting
+  // with WNOWAIT won't cause the child process to be reaped, so the kernel
+  // won't reuse the pid until the Wait call below, and any kill signals won't
+  // reach unexpected processes.
+
+  subprocess_ = {};
+
+  if (infop.si_code == CLD_EXITED && bin == kStopBin) {
+    instance_manager_.RemoveInstanceGroup(home);
+  }
+
+  if (infop.si_code == CLD_EXITED && infop.si_status == 0) {
+    response.mutable_status()->set_code(cvd::Status::OK);
+    return response;
+  }
+
+  response.mutable_status()->set_code(cvd::Status::INTERNAL);
+  if (infop.si_code == CLD_EXITED) {
+    response.mutable_status()->set_message("Exited with code " +
+                                           std::to_string(infop.si_status));
+  } else if (infop.si_code == CLD_KILLED) {
+    response.mutable_status()->set_message("Exited with signal " +
+                                           std::to_string(infop.si_status));
+  } else {
+    response.mutable_status()->set_message("Quit with code " +
+                                           std::to_string(infop.si_status));
+  }
+  return response;
+}
+
+Result<void> CvdCommandHandler::Interrupt() {
+  std::scoped_lock interrupt_lock(interruptible_);
+  if (subprocess_) {
+    auto stop_result = subprocess_->Stop();
+    switch (stop_result) {
+      case StopperResult::kStopFailure:
+        return CF_ERR("Failed to stop subprocess");
+      case StopperResult::kStopCrash:
+        return CF_ERR("Stopper caused process to crash");
+      case StopperResult::kStopSuccess:
+        return {};
+      default:
+        return CF_ERR("Unknown stop result: " << (uint64_t)stop_result);
+    }
+  }
+  return {};
+}
+
+CommandInvocation ParseInvocation(const cvd::Request& request) {
+  CommandInvocation invocation;
+  if (request.contents_case() != cvd::Request::ContentsCase::kCommandRequest) {
+    return invocation;
+  }
+  if (request.command_request().args_size() == 0) {
+    return invocation;
+  }
+  for (const std::string& arg : request.command_request().args()) {
+    invocation.arguments.push_back(arg);
+  }
+  invocation.arguments[0] = cpp_basename(invocation.arguments[0]);
+  if (invocation.arguments[0] == "cvd") {
+    if (invocation.arguments.size() == 1) {
+      // Show help if user invokes `cvd` alone.
+      invocation.command = "help";
+      invocation.arguments = {};
+    } else {  // More arguments
+      invocation.command = invocation.arguments[1];
+      invocation.arguments.erase(invocation.arguments.begin());
+      invocation.arguments.erase(invocation.arguments.begin());
+    }
+  } else {
+    invocation.command = invocation.arguments[0];
+    invocation.arguments.erase(invocation.arguments.begin());
+  }
+  return invocation;
+}
+
+fruit::Component<fruit::Required<InstanceManager>> cvdCommandComponent() {
+  return fruit::createComponent()
+      .addMultibinding<CvdServerHandler, CvdCommandHandler>();
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/cvd/server_constants.h
similarity index 61%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/cvd/server_constants.h
index 9f25445..300c8c4 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/cvd/server_constants.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-#include "common/libs/utils/size_utils.h"
-
-#include <unistd.h>
-
 namespace cuttlefish {
+namespace cvd {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+// Major version uprevs are backwards incompatible.
+// Minor version uprevs are backwards compatible within major version.
+constexpr int kVersionMajor = 1;
+constexpr int kVersionMinor = 1;
 
+// Pathname of the abstract cvd_server socket.
+constexpr char kServerSocketPath[] = "cvd_server";
+
+}  // namespace cvd
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/server_shutdown.cpp b/host/commands/cvd/server_shutdown.cpp
new file mode 100644
index 0000000..ec7af0b
--- /dev/null
+++ b/host/commands/cvd/server_shutdown.cpp
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+#include "host/commands/cvd/server.h"
+
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
+#include "host/commands/cvd/instance_manager.h"
+
+namespace cuttlefish {
+namespace {
+
+class CvdShutdownHandler : public CvdServerHandler {
+ public:
+  INJECT(CvdShutdownHandler(CvdServer& server,
+                            InstanceManager& instance_manager))
+      : server_(server), instance_manager_(instance_manager) {}
+
+  Result<bool> CanHandle(const RequestWithStdio& request) const override {
+    return request.Message().contents_case() ==
+           cvd::Request::ContentsCase::kShutdownRequest;
+  }
+
+  Result<cvd::Response> Handle(const RequestWithStdio& request) override {
+    CF_EXPECT(CanHandle(request));
+    cvd::Response response;
+    response.mutable_shutdown_response();
+
+    if (!request.Extra()) {
+      response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION);
+      response.mutable_status()->set_message(
+          "Missing extra SharedFD for shutdown");
+      return response;
+    }
+
+    if (request.Message().shutdown_request().clear()) {
+      *response.mutable_status() =
+          instance_manager_.CvdClear(request.Out(), request.Err());
+      if (response.status().code() != cvd::Status::OK) {
+        return response;
+      }
+    }
+
+    if (instance_manager_.HasInstanceGroups()) {
+      response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION);
+      response.mutable_status()->set_message(
+          "Cannot shut down cvd_server while devices are being tracked. "
+          "Try `cvd kill-server`.");
+      return response;
+    }
+
+    // Intentionally leak the write_pipe fd so that it only closes
+    // when this process fully exits.
+    (*request.Extra())->UNMANAGED_Dup();
+
+    WriteAll(request.Out(), "Stopping the cvd_server.\n");
+    server_.Stop();
+
+    response.mutable_status()->set_code(cvd::Status::OK);
+    return response;
+  }
+
+  Result<void> Interrupt() override { return CF_ERR("Can't interrupt"); }
+
+ private:
+  CvdServer& server_;
+  InstanceManager& instance_manager_;
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<CvdServer, InstanceManager>>
+cvdShutdownComponent() {
+  return fruit::createComponent()
+      .addMultibinding<CvdServerHandler, CvdShutdownHandler>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd/server_version.cpp b/host/commands/cvd/server_version.cpp
new file mode 100644
index 0000000..2168c0f
--- /dev/null
+++ b/host/commands/cvd/server_version.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#include "host/commands/cvd/server.h"
+
+#include <build/version.h>
+#include <fruit/fruit.h>
+
+#include "cvd_server.pb.h"
+
+#include "common/libs/utils/result.h"
+#include "host/commands/cvd/server_constants.h"
+#include "host/libs/config/host_tools_version.h"
+
+namespace cuttlefish {
+namespace {
+
+class CvdVersionHandler : public CvdServerHandler {
+ public:
+  INJECT(CvdVersionHandler()) = default;
+
+  Result<bool> CanHandle(const RequestWithStdio& request) const override {
+    return request.Message().contents_case() ==
+           cvd::Request::ContentsCase::kVersionRequest;
+  }
+
+  Result<cvd::Response> Handle(const RequestWithStdio& request) override {
+    CF_EXPECT(CanHandle(request));
+    cvd::Response response;
+    auto& version = *response.mutable_version_response()->mutable_version();
+    version.set_major(cvd::kVersionMajor);
+    version.set_minor(cvd::kVersionMinor);
+    version.set_build(android::build::GetBuildNumber());
+    version.set_crc32(FileCrc("/proc/self/exe"));
+    response.mutable_status()->set_code(cvd::Status::OK);
+    return response;
+  }
+
+  Result<void> Interrupt() override { return CF_ERR("Can't interrupt"); }
+};
+
+}  // namespace
+
+fruit::Component<> cvdVersionComponent() {
+  return fruit::createComponent()
+      .addMultibinding<CvdServerHandler, CvdVersionHandler>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd_send_sms/Android.bp b/host/commands/cvd_send_sms/Android.bp
new file mode 100644
index 0000000..739aecd
--- /dev/null
+++ b/host/commands/cvd_send_sms/Android.bp
@@ -0,0 +1,64 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+    name: "cvd_send_sms_defaults",
+    shared_libs: [
+        "libbase",
+        "libcuttlefish_fs",
+        "liblog",
+        "libicuuc",
+    ],
+    defaults: ["cuttlefish_buildhost_only"],
+}
+
+cc_library_static {
+    name: "libcvd_send_sms",
+    srcs: [
+        "sms_sender.cc",
+        "pdu_format_builder.cc"
+    ],
+    defaults: ["cvd_send_sms_defaults"],
+}
+
+cc_binary {
+    name: "cvd_send_sms",
+    srcs: [
+        "main.cc"
+    ],
+    static_libs: [
+        "libcvd_send_sms",
+        "libgflags",
+    ],
+    defaults: ["cvd_send_sms_defaults"],
+}
+
+cc_test_host {
+    name: "cvd_send_sms_test",
+    srcs: [
+        "unittest/main_test.cc",
+        "unittest/sms_sender_test.cc",
+        "unittest/pdu_format_builder_test.cc",
+    ],
+    static_libs: [
+        "libgmock",
+        "libcvd_send_sms",
+    ],
+    defaults: ["cvd_send_sms_defaults"],
+}
diff --git a/host/commands/cvd_send_sms/main.cc b/host/commands/cvd_send_sms/main.cc
new file mode 100644
index 0000000..058b355
--- /dev/null
+++ b/host/commands/cvd_send_sms/main.cc
@@ -0,0 +1,48 @@
+#include "gflags/gflags.h"
+
+#include "android-base/logging.h"
+#include "common/libs/fs/shared_fd.h"
+#include "host/commands/cvd_send_sms/sms_sender.h"
+
+DEFINE_string(sender_number, "+16501234567",
+              "sender phone number in E.164 format");
+DEFINE_uint32(instance_number, 1,
+              "number of the cvd instance to send the sms to, default is 1");
+DEFINE_uint32(modem_id, 0,
+              "modem id needed for multisim devices, default is 0");
+
+namespace cuttlefish {
+namespace {
+
+// Usage examples:
+//   * cvd_send_sms "hello world"
+//   * cvd_send_sms --sender_number="+16501239999" "hello world"
+//   * cvd_send_sms --sender_number="16501239999" "hello world"
+//   * cvd_send_sms --instance-number=2 "hello world"
+//   * cvd_send_sms --instance-number=2 --modem_id=1 "hello world"
+
+int SendSmsMain(int argc, char** argv) {
+  gflags::ParseCommandLineFlags(&argc, &argv, true);
+  if (argc == 1) {
+    LOG(ERROR) << "Missing message content. First positional argument is used "
+                  "as the message content, `cvd_send_sms --instance-number=2 "
+                  "\"hello world\"`";
+    return -1;
+  }
+  // Builds the name of the corresponding modem simulator monitor socket.
+  // https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/modem_simulator/main.cpp;l=115;drc=cbfe7dba44bfea95049152b828c1a5d35c9e0522
+  std::string socket_name = std::string("modem_simulator") +
+                            std::to_string(1000 + FLAGS_instance_number);
+  auto client_socket = cuttlefish::SharedFD::SocketLocalClient(
+      socket_name.c_str(), /* abstract */ true, SOCK_STREAM);
+  SmsSender sms_sender(client_socket);
+  if (!sms_sender.Send(argv[1], FLAGS_sender_number, FLAGS_modem_id)) {
+    return -1;
+  }
+  return 0;
+}
+
+}  // namespace
+}  // namespace cuttlefish
+
+int main(int argc, char** argv) { return cuttlefish::SendSmsMain(argc, argv); }
diff --git a/host/commands/cvd_send_sms/pdu_format_builder.cc b/host/commands/cvd_send_sms/pdu_format_builder.cc
new file mode 100644
index 0000000..943819d
--- /dev/null
+++ b/host/commands/cvd_send_sms/pdu_format_builder.cc
@@ -0,0 +1,167 @@
+#include "host/commands/cvd_send_sms/pdu_format_builder.h"
+
+#include <algorithm>
+#include <codecvt>
+#include <cstddef>
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <regex>
+#include <sstream>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "unicode/uchriter.h"
+#include "unicode/unistr.h"
+#include "unicode/ustring.h"
+
+namespace cuttlefish {
+
+namespace {
+// 3GPP TS 23.038 V9.1.1 section 6.2.1 - GSM 7 bit Default Alphabet
+// https://www.etsi.org/deliver/etsi_ts/123000_123099/123038/09.01.01_60/ts_123038v090101p.pdf
+// clang-format off
+const std::vector<std::string> kGSM7BitDefaultAlphabet = {
+  "@", "£", "$", "¥", "è", "é", "ù", "ì", "ò", "Ç", "\n", "Ø", "ø", "\r", "Å", "å",
+  "Δ", "_", "Φ", "Γ", "Λ", "Ω", "Π", "Ψ", "Σ", "Θ", "Ξ", u8"\uffff" /*ESC*/, "Æ", "æ", "ß", "É",
+  " ", "!", "\"", "#", "¤", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
+  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
+  "¡", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
+  "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Ä", "Ö", "Ñ", "Ü", "§",
+  "¿", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
+  "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ä", "ö", "ñ", "ü", "à",
+};
+// clang-format on
+
+// Encodes using the GSM 7bit encoding as defined in 3GPP TS 23.038
+// https://www.etsi.org/deliver/etsi_ts/123000_123099/123038/09.01.01_60/ts_123038v090101p.pdf
+static std::string Gsm7bitEncode(const std::string& input) {
+  icu::UnicodeString unicode_str(input.c_str());
+  icu::UCharCharacterIterator iter(unicode_str.getTerminatedBuffer(),
+                                   unicode_str.length());
+  size_t octects_size = unicode_str.length() - (unicode_str.length() / 8);
+  std::byte octects[octects_size];
+  std::byte* octects_index = octects;
+  int bits_to_write_in_prev_octect = 0;
+  for (; iter.hasNext(); iter.next()) {
+    UChar uchar = iter.current();
+    char dest[5];
+    UErrorCode uerror_code;
+    u_strToUTF8(dest, 5, NULL, &uchar, 1, &uerror_code);
+    if (U_FAILURE(uerror_code)) {
+      LOG(ERROR) << "u_strToUTF8 failed with error: "
+                 << u_errorName(uerror_code) << ", with string: " << input;
+      return "";
+    }
+    std::string character(dest);
+    auto found_it = std::find(kGSM7BitDefaultAlphabet.begin(),
+                              kGSM7BitDefaultAlphabet.end(), character);
+    if (found_it == kGSM7BitDefaultAlphabet.end()) {
+      LOG(ERROR) << "Character: " << character
+                 << " does not exist in GSM 7 bit Default Alphabet";
+      return "";
+    }
+    std::byte code =
+        (std::byte)std::distance(kGSM7BitDefaultAlphabet.begin(), found_it);
+    if (iter.hasPrevious()) {
+      std::byte prev_octect_value = *(octects_index - 1);
+      // Writes the corresponding lowest part in the previous octect.
+      *(octects_index - 1) =
+          code << (8 - bits_to_write_in_prev_octect) | prev_octect_value;
+    }
+    if (bits_to_write_in_prev_octect < 7) {
+      // Writes the remaining highest part in the current octect.
+      *octects_index = code >> bits_to_write_in_prev_octect;
+      bits_to_write_in_prev_octect++;
+      octects_index++;
+    } else {  // bits_to_write_in_prev_octect == 7
+      // The 7 bits of the current character were fully packed into the
+      // previous octect.
+      bits_to_write_in_prev_octect = 0;
+    }
+  }
+  std::stringstream result;
+  for (int i = 0; i < octects_size; i++) {
+    result << std::setfill('0') << std::setw(2) << std::hex
+           << std::to_integer<int>(octects[i]);
+  }
+  return result.str();
+}
+
+// Validates whether the passed phone number conforms to the E.164 specs,
+// https://www.itu.int/rec/T-REC-E.164
+static bool IsValidE164PhoneNumber(const std::string& number) {
+  const static std::regex e164_regex("^\\+?[1-9]\\d{1,14}$");
+  return std::regex_match(number, e164_regex);
+}
+
+// Encodes numeric values by using the Semi-Octect representation.
+static std::string SemiOctectsEncode(const std::string& input) {
+  bool length_is_odd = input.length() % 2 == 1;
+  int end = length_is_odd ? input.length() - 1 : input.length();
+  std::stringstream ss;
+  for (int i = 0; i < end; i += 2) {
+    ss << input[i + 1];
+    ss << input[i];
+  }
+  if (length_is_odd) {
+    ss << "f";
+    ss << input[input.length() - 1];
+  }
+  return ss.str();
+}
+
+// Converts to hexadecimal representation filling with a leading 0 if
+// necessary.
+static std::string DecimalToHexString(int number) {
+  std::stringstream ss;
+  ss << std::setfill('0') << std::setw(2) << std::hex << number;
+  return ss.str();
+}
+}  // namespace
+
+void PDUFormatBuilder::SetUserData(const std::string& user_data) {
+  user_data_ = user_data;
+}
+
+void PDUFormatBuilder::SetSenderNumber(const std::string& sender_number) {
+  sender_number_ = sender_number;
+}
+
+std::string PDUFormatBuilder::Build() {
+  if (user_data_.empty()) {
+    LOG(ERROR) << "Empty user data.";
+    return "";
+  }
+  if (sender_number_.empty()) {
+    LOG(ERROR) << "Empty sender phone number.";
+    return "";
+  }
+  if (!IsValidE164PhoneNumber(sender_number_)) {
+    LOG(ERROR) << "Sender phone number"
+               << " \"" << sender_number_ << "\" "
+               << "does not conform with the E.164 format";
+    return "";
+  }
+  std::string sender_number_without_plus =
+      sender_number_[0] == '+' ? sender_number_.substr(1) : sender_number_;
+  int ulength = icu::UnicodeString(user_data_.c_str()).length();
+  if (ulength > 160) {
+    LOG(ERROR) << "Invalid user data as it has more than 160 characters: "
+               << user_data_;
+    return "";
+  }
+  std::string encoded = Gsm7bitEncode(user_data_);
+  if (encoded.empty()) {
+    return "";
+  }
+  std::stringstream ss;
+  ss << "000100" << DecimalToHexString(sender_number_without_plus.length())
+     << "91"  // 91 indicates international phone number format.
+     << SemiOctectsEncode(sender_number_without_plus)
+     << "00"  // TP-PID. Protocol identifier
+     << "00"  // TP-DCS. Data coding scheme. The GSM 7bit default alphabet.
+     << DecimalToHexString(ulength) << encoded;
+  return ss.str();
+}
+}  // namespace cuttlefish
diff --git a/host/commands/cvd_send_sms/pdu_format_builder.h b/host/commands/cvd_send_sms/pdu_format_builder.h
new file mode 100644
index 0000000..8b5ce76
--- /dev/null
+++ b/host/commands/cvd_send_sms/pdu_format_builder.h
@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <string>
+
+namespace cuttlefish {
+
+// Builds PDU format strings used to send SMS to Cuttlefish modem simulator.
+//
+// PDU format is specified by the Etsi organization in GSM 03.40
+// https://www.etsi.org/deliver/etsi_gts/03/0340/05.03.00_60/gsmts_0340v050300p.pdf
+//
+// The resulting PDU format string encapsulates different parameters
+// values like:
+// * The phone number.
+// * Data coding scheme. 7 bit Alphabet or 8 bit (used in e.g. smart
+// messaging, OTA provisioning etc)
+// * User data.
+//
+// NOTE: For sender phone number, only international numbers following the
+// E.164 format (https://www.itu.int/rec/T-REC-E.164) are supported.
+//
+// NOTE: The coding scheme is not parameterized yet using always the 7bit
+// Alphabet coding scheme.
+class PDUFormatBuilder {
+ public:
+  void SetUserData(const std::string& user_data);
+  void SetSenderNumber(const std::string& number);
+  // Returns the corresponding PDU format string, returns an empty string if
+  // the User Data or the Sender Number set are invalid.
+  std::string Build();
+
+ private:
+  std::string user_data_;
+  std::string sender_number_;
+};
+
+}  // namespace cuttlefish
diff --git a/host/commands/cvd_send_sms/sms_sender.cc b/host/commands/cvd_send_sms/sms_sender.cc
new file mode 100644
index 0000000..5c8b8db
--- /dev/null
+++ b/host/commands/cvd_send_sms/sms_sender.cc
@@ -0,0 +1,44 @@
+#include "host/commands/cvd_send_sms/sms_sender.h"
+
+#include <algorithm>
+#include <codecvt>
+#include <cstddef>
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "common/libs/fs/shared_buf.h"
+#include "host/commands/cvd_send_sms/pdu_format_builder.h"
+
+namespace cuttlefish {
+
+SmsSender::SmsSender(SharedFD modem_simulator_client_fd)
+    : modem_simulator_client_fd_(modem_simulator_client_fd) {}
+
+bool SmsSender::Send(const std::string& content,
+                     const std::string& sender_number, uint32_t modem_id) {
+  if (!modem_simulator_client_fd_->IsOpen()) {
+    LOG(ERROR) << "Failed to connect to remote modem simulator, error: "
+               << modem_simulator_client_fd_->StrError();
+    return false;
+  }
+  PDUFormatBuilder builder;
+  builder.SetUserData(content);
+  builder.SetSenderNumber(sender_number);
+  std::string pdu_format_str = builder.Build();
+  if (pdu_format_str.empty()) {
+    return false;
+  }
+  // https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/modem_simulator/main.cpp;l=151;drc=cbfe7dba44bfea95049152b828c1a5d35c9e0522
+  std::string at_command = "REM" + std::to_string(modem_id) +
+                           "AT+REMOTESMS=" + pdu_format_str + "\r";
+  if (WriteAll(modem_simulator_client_fd_, at_command) != at_command.size()) {
+    LOG(ERROR) << "Error writing to socket: "
+               << modem_simulator_client_fd_->StrError();
+    return false;
+  }
+  return true;
+}
+}  // namespace cuttlefish
diff --git a/host/commands/cvd_send_sms/sms_sender.h b/host/commands/cvd_send_sms/sms_sender.h
new file mode 100644
index 0000000..102d636
--- /dev/null
+++ b/host/commands/cvd_send_sms/sms_sender.h
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+
+class SmsSender {
+ public:
+  SmsSender(SharedFD modem_simulator_client_fd);
+
+  // Returns true if SMS was successfully sent, returns false otherwise.
+  bool Send(const std::string& sms_body, const std::string& sender_number,
+            uint32_t modem_id = 0);
+
+ private:
+  SharedFD modem_simulator_client_fd_;
+};
+}  // namespace cuttlefish
diff --git a/host/commands/tapsetiff/Android.bp b/host/commands/cvd_send_sms/unittest/main_test.cc
similarity index 74%
rename from host/commands/tapsetiff/Android.bp
rename to host/commands/cvd_send_sms/unittest/main_test.cc
index 1d7dedb..d2ceeb7 100644
--- a/host/commands/tapsetiff/Android.bp
+++ b/host/commands/cvd_send_sms/unittest/main_test.cc
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2020 The Android Open Source Project
+// 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.
@@ -13,11 +13,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
+#include <gtest/gtest.h>
 
-sh_binary_host {
-    name: "tapsetiff",
-    src: "tapsetiff.py",
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
 }
diff --git a/host/commands/cvd_send_sms/unittest/pdu_format_builder_test.cc b/host/commands/cvd_send_sms/unittest/pdu_format_builder_test.cc
new file mode 100644
index 0000000..6d62c0b
--- /dev/null
+++ b/host/commands/cvd_send_sms/unittest/pdu_format_builder_test.cc
@@ -0,0 +1,223 @@
+//
+// 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.
+
+#include "host/commands/cvd_send_sms/pdu_format_builder.h"
+
+#include <gtest/gtest.h>
+
+namespace cuttlefish {
+namespace {
+
+TEST(PDUFormatBuilderTest, EmptyUserDataFails) {
+  PDUFormatBuilder builder;
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(result, "");
+}
+
+TEST(PDUFormatBuilderTest, NotInAlphabetCharacterFails) {
+  PDUFormatBuilder builder;
+  builder.SetUserData("ccccccc☺");
+  builder.SetSenderNumber("+16501234567");
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(result, "");
+}
+
+TEST(PDUFormatBuilderTest, With161CharactersFails) {
+  PDUFormatBuilder builder;
+  builder.SetUserData(
+      "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+      "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+      "ccccccccccccccccccccccccccccccccccccccccc");
+  builder.SetSenderNumber("+16501234567");
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(result, "");
+}
+
+TEST(PDUFormatBuilderTest, With1CharacterSucceeds) {
+  PDUFormatBuilder builder;
+  builder.SetUserData("c");
+  builder.SetSenderNumber("+16501234567");
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(result, "0001000b916105214365f700000163");
+}
+
+TEST(PDUFormatBuilderTest, With7CharactersSucceeds) {
+  PDUFormatBuilder builder;
+  builder.SetUserData("ccccccc");
+  builder.SetSenderNumber("+16501234567");
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(result, "0001000b916105214365f7000007e3f1783c1e8f01");
+}
+
+TEST(PDUFormatBuilderTest, With8CharactersSucceeds) {
+  PDUFormatBuilder builder;
+  builder.SetUserData("cccccccc");
+  builder.SetSenderNumber("+16501234567");
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(result, "0001000b916105214365f7000008e3f1783c1e8fc7");
+}
+
+TEST(PDUFormatBuilderTest, With160CharactersSucceeds) {
+  PDUFormatBuilder builder;
+  builder.SetUserData(
+      "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+      "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+      "cccccccccccccccccccccccccccccccccccccccc");
+  builder.SetSenderNumber("+16501234567");
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(result,
+            "0001000b916105214365f70000a0"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7"
+            "e3f1783c1e8fc7");
+}
+
+TEST(PDUFormatBuilderTest, With160MultiByteCharactersSucceeds) {
+  PDUFormatBuilder builder;
+  builder.SetUserData(
+      "ΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩ"
+      "ΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩ"
+      "ΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩΩ");
+  builder.SetSenderNumber("+16501234567");
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(result,
+            "0001000b916105214365f70000a0"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a"
+            "954aa552a9542a");
+}
+
+TEST(PDUFormatBuilderTest, FullAlphabetSucceeds) {
+  PDUFormatBuilder builder;
+  builder.SetUserData(
+      "@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞ\uffffÆæßÉ "
+      "!\"#¤%&'()*+,-./"
+      "0123456789:;<=>?"
+      "¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà");
+  builder.SetSenderNumber("+16501234567");
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(
+      result,
+      "0001000b916105214365f70000808080604028180e888462c168381e90886442a9582e98"
+      "8c66c3e9783ea09068442a994ea8946ac56ab95eb0986c46abd96eb89c6ec7ebf97ec0a0"
+      "70482c1a8fc8a472c96c3a9fd0a8744aad5aafd8ac76cbed7abfe0b0784c2e9bcfe8b47a"
+      "cd6ebbdff0b87c4eafdbeff8bc7ecfeffbff");
+}
+
+TEST(PDUFormatBuilderTest, WithEmptySenderPhoneNumberFails) {
+  PDUFormatBuilder builder;
+  builder.SetUserData("c");
+  builder.SetSenderNumber("");
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(result, "");
+}
+
+TEST(PDUFormatBuilderTest, WithInvalidSenderPhoneNumberFails) {
+  std::vector<std::string> numbers{"06501234567", "1", "1650603619399999"};
+  PDUFormatBuilder builder;
+  builder.SetUserData("c");
+
+  for (auto n : numbers) {
+    builder.SetSenderNumber(n);
+    EXPECT_EQ(builder.Build(), "");
+  }
+}
+
+TEST(PDUFormatBuilderTest, WithoutLeadingPlusSignSucceeds) {
+  PDUFormatBuilder builder;
+  builder.SetUserData("c");
+  builder.SetSenderNumber("16501234567");
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(result, "0001000b916105214365f700000163");
+}
+
+TEST(PDUFormatBuilderTest, WithOddSenderPhoneNumberLengthSucceeds) {
+  PDUFormatBuilder builder;
+  builder.SetUserData("c");
+  builder.SetSenderNumber("+16501234567");
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(result, "0001000b916105214365f700000163");
+}
+
+TEST(PDUFormatBuilderTest, WithEvenSenderPhoneNumberLengthSucceeds) {
+  PDUFormatBuilder builder;
+  builder.SetUserData("c");
+  builder.SetSenderNumber("+526501234567");
+
+  std::string result = builder.Build();
+
+  EXPECT_EQ(result, "0001000c9125561032547600000163");
+}
+
+}  // namespace
+}  // namespace cuttlefish
diff --git a/host/commands/cvd_send_sms/unittest/sms_sender_test.cc b/host/commands/cvd_send_sms/unittest/sms_sender_test.cc
new file mode 100644
index 0000000..e2dce44
--- /dev/null
+++ b/host/commands/cvd_send_sms/unittest/sms_sender_test.cc
@@ -0,0 +1,84 @@
+//
+// 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.
+
+#include "host/commands/cvd_send_sms/sms_sender.h"
+
+#include <sstream>
+
+#include <android-base/logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+namespace {
+
+class SmsSenderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    CHECK(SharedFD::SocketPair(AF_LOCAL, SOCK_STREAM, 0, &client_fd_,
+                               &fake_server_fd_))
+        << strerror(errno);
+    CHECK(client_fd_->IsOpen());
+    CHECK(fake_server_fd_->IsOpen());
+  }
+
+  void AssertCommandIsSent(std::string expected_command) {
+    std::stringstream ss;
+    std::vector<char> buffer(4096);
+    ssize_t bytes_read;
+    do {
+      bytes_read = fake_server_fd_->Read(buffer.data(), buffer.size());
+      CHECK(bytes_read >= 0) << strerror(errno);
+      ss << std::string(buffer.data(), bytes_read);
+    } while (buffer[bytes_read - 1] != '\r');
+    EXPECT_THAT(ss.str(), testing::Eq(expected_command));
+  }
+
+  SharedFD client_fd_;
+  SharedFD fake_server_fd_;
+};
+
+TEST_F(SmsSenderTest, InvalidContentFails) {
+  SmsSender sender(client_fd_);
+
+  bool result = sender.Send("", "+16501234567");
+
+  EXPECT_FALSE(result);
+}
+
+TEST_F(SmsSenderTest, ValidContentSucceeds) {
+  SmsSender sender(client_fd_);
+
+  bool result = sender.Send("hellohello", "+16501234567");
+
+  EXPECT_TRUE(result);
+  AssertCommandIsSent(
+      "REM0AT+REMOTESMS=0001000b916105214365f700000ae8329bfd4697d9ec37\r");
+}
+
+TEST_F(SmsSenderTest, NonDefaultModemIdValueSucceeds) {
+  SmsSender sender(client_fd_);
+
+  bool result = sender.Send("hellohello", "+16501234567", 1);
+
+  EXPECT_TRUE(result);
+  AssertCommandIsSent(
+      "REM1AT+REMOTESMS=0001000b916105214365f700000ae8329bfd4697d9ec37\r");
+}
+
+}  // namespace
+}  // namespace cuttlefish
diff --git a/host/commands/cvd_status/Android.bp b/host/commands/cvd_status/Android.bp
deleted file mode 100644
index ae48d19..0000000
--- a/host/commands/cvd_status/Android.bp
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_binary {
-    name: "cvd_status",
-    srcs: [
-        "cvd_status.cc",
-    ],
-    shared_libs: [
-        "libbase",
-        "libcuttlefish_fs",
-        "libcuttlefish_utils",
-        "libjsoncpp",
-    ],
-    static_libs: [
-        "libcuttlefish_host_config",
-        "libcuttlefish_vm_manager",
-        "libgflags",
-    ],
-    defaults: ["cuttlefish_host", "cuttlefish_libicuuc"],
-}
diff --git a/host/commands/cvd_status/cvd_status.cc b/host/commands/cvd_status/cvd_status.cc
deleted file mode 100644
index 81436d4..0000000
--- a/host/commands/cvd_status/cvd_status.cc
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <inttypes.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <signal.h>
-
-#include <algorithm>
-#include <cstdlib>
-#include <fstream>
-#include <iomanip>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include <gflags/gflags.h>
-#include <android-base/logging.h>
-
-#include "common/libs/fs/shared_fd.h"
-#include "common/libs/fs/shared_select.h"
-#include "common/libs/utils/environment.h"
-#include "host/commands/run_cvd/runner_defs.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/vm_manager/vm_manager.h"
-
-DEFINE_int32(wait_for_launcher, 5,
-             "How many seconds to wait for the launcher to respond to the status "
-             "command. A value of zero means wait indefinetly");
-
-int main(int argc, char** argv) {
-  ::android::base::InitLogging(argv, android::base::StderrLogger);
-  google::ParseCommandLineFlags(&argc, &argv, true);
-
-  auto config = cuttlefish::CuttlefishConfig::Get();
-  if (!config) {
-    LOG(ERROR) << "Failed to obtain config object";
-    return 1;
-  }
-
-  auto instance = config->ForDefaultInstance();
-  auto monitor_path = instance.launcher_monitor_socket_path();
-  if (monitor_path.empty()) {
-    LOG(ERROR) << "No path to launcher monitor found";
-    return 2;
-  }
-  auto monitor_socket = cuttlefish::SharedFD::SocketLocalClient(
-      monitor_path.c_str(), false, SOCK_STREAM, FLAGS_wait_for_launcher);
-  if (!monitor_socket->IsOpen()) {
-    LOG(ERROR) << "Unable to connect to launcher monitor at " << monitor_path
-               << ": " << monitor_socket->StrError();
-    return 3;
-  }
-  auto request = cuttlefish::LauncherAction::kStatus;
-  auto bytes_sent = monitor_socket->Send(&request, sizeof(request), 0);
-  if (bytes_sent < 0) {
-    LOG(ERROR) << "Error sending launcher monitor the status command: "
-               << monitor_socket->StrError();
-    return 4;
-  }
-  // Perform a select with a timeout to guard against launcher hanging
-  cuttlefish::SharedFDSet read_set;
-  read_set.Set(monitor_socket);
-  struct timeval timeout = {FLAGS_wait_for_launcher, 0};
-  int selected = cuttlefish::Select(&read_set, nullptr, nullptr,
-                             FLAGS_wait_for_launcher <= 0 ? nullptr : &timeout);
-  if (selected < 0){
-    LOG(ERROR) << "Failed communication with the launcher monitor: "
-               << strerror(errno);
-    return 5;
-  }
-  if (selected == 0) {
-    LOG(ERROR) << "Timeout expired waiting for launcher monitor to respond";
-    return 6;
-  }
-  cuttlefish::LauncherResponse response;
-  auto bytes_recv = monitor_socket->Recv(&response, sizeof(response), 0);
-  if (bytes_recv < 0) {
-    LOG(ERROR) << "Error receiving response from launcher monitor: "
-               << monitor_socket->StrError();
-    return 7;
-  }
-  if (response != cuttlefish::LauncherResponse::kSuccess) {
-    LOG(ERROR) << "Received '" << static_cast<char>(response)
-               << "' response from launcher monitor";
-    return 8;
-  }
-  LOG(INFO) << "run_cvd is active.";
-  return 0;
-}
diff --git a/host/commands/fetcher/Android.bp b/host/commands/fetcher/Android.bp
index fdb97ec..6555a74 100644
--- a/host/commands/fetcher/Android.bp
+++ b/host/commands/fetcher/Android.bp
@@ -20,15 +20,13 @@
 cc_binary {
     name: "fetch_cvd",
     srcs: [
-        "build_api.cc",
-        "credential_source.cc",
-        "curl_wrapper.cc",
         "fetch_cvd.cc",
-        "install_zip.cc",
     ],
     static_libs: [
+        "libcuttlefish_web",
         "libcuttlefish_host_config",
         "libgflags",
+        "libext2_blkid",
     ],
     target: {
         host: {
diff --git a/host/commands/fetcher/build_api.cc b/host/commands/fetcher/build_api.cc
deleted file mode 100644
index 16f95db..0000000
--- a/host/commands/fetcher/build_api.cc
+++ /dev/null
@@ -1,256 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "build_api.h"
-
-#include <dirent.h>
-#include <unistd.h>
-
-#include <chrono>
-#include <set>
-#include <string>
-#include <thread>
-
-#include <android-base/strings.h>
-#include <android-base/logging.h>
-
-#include "common/libs/utils/environment.h"
-#include "common/libs/utils/files.h"
-
-namespace cuttlefish {
-namespace {
-
-const std::string BUILD_API =
-    "https://www.googleapis.com/android/internal/build/v3";
-
-bool StatusIsTerminal(const std::string& status) {
-  const static std::set<std::string> terminal_statuses = {
-    "abandoned",
-    "complete",
-    "error",
-    "ABANDONED",
-    "COMPLETE",
-    "ERROR",
-  };
-  return terminal_statuses.count(status) > 0;
-}
-
-} // namespace
-
-Artifact::Artifact(const Json::Value& json_artifact) {
-  name = json_artifact["name"].asString();
-  size = std::stol(json_artifact["size"].asString());
-  last_modified_time = std::stol(json_artifact["lastModifiedTime"].asString());
-  md5 = json_artifact["md5"].asString();
-  content_type = json_artifact["contentType"].asString();
-  revision = json_artifact["revision"].asString();
-  creation_time = std::stol(json_artifact["creationTime"].asString());
-  crc32 = json_artifact["crc32"].asUInt();
-}
-
-std::ostream& operator<<(std::ostream& out, const DeviceBuild& build) {
-  return out << "(id=\"" << build.id << "\", target=\"" << build.target << "\")";
-}
-
-std::ostream& operator<<(std::ostream& out, const DirectoryBuild& build) {
-  auto paths = android::base::Join(build.paths, ":");
-  return out << "(paths=\"" << paths << "\", target=\"" << build.target << "\")";
-}
-
-std::ostream& operator<<(std::ostream& out, const Build& build) {
-  std::visit([&out](auto&& arg) { out << arg; }, build);
-  return out;
-}
-
-DirectoryBuild::DirectoryBuild(const std::vector<std::string>& paths,
-                               const std::string& target)
-    : paths(paths), target(target), id("eng") {
-  product = StringFromEnv("TARGET_PRODUCT", "");
-}
-
-BuildApi::BuildApi(std::unique_ptr<CredentialSource> credential_source)
-    : credential_source(std::move(credential_source)) {}
-
-std::vector<std::string> BuildApi::Headers() {
-  std::vector<std::string> headers;
-  if (credential_source) {
-    headers.push_back("Authorization:Bearer " + credential_source->Credential());
-  }
-  return headers;
-}
-
-std::string BuildApi::LatestBuildId(const std::string& branch,
-                                    const std::string& target) {
-  std::string url = BUILD_API + "/builds?branch=" + branch
-      + "&buildAttemptStatus=complete"
-      + "&buildType=submitted&maxResults=1&successful=true&target=" + target;
-  auto response = curl.DownloadToJson(url, Headers());
-  CHECK(!response.isMember("error")) << "Error fetching the latest build of \""
-      << target << "\" on \"" << branch << "\". Response was " << response;
-
-  if (!response.isMember("builds") || response["builds"].size() != 1) {
-    LOG(WARNING) << "expected to receive 1 build for \"" << target << "\" on \""
-        << branch << "\", but received " << response["builds"].size()
-        << ". Full response was " << response;
-    return "";
-  }
-  return response["builds"][0]["buildId"].asString();
-}
-
-std::string BuildApi::BuildStatus(const DeviceBuild& build) {
-  std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target;
-  auto response_json = curl.DownloadToJson(url, Headers());
-  CHECK(!response_json.isMember("error")) << "Error fetching the status of "
-      << "build " << build << ". Response was " << response_json;
-
-  return response_json["buildAttemptStatus"].asString();
-}
-
-std::string BuildApi::ProductName(const DeviceBuild& build) {
-  std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target;
-  auto response_json = curl.DownloadToJson(url, Headers());
-  CHECK(!response_json.isMember("error")) << "Error fetching the status of "
-      << "build " << build << ". Response was " << response_json;
-  CHECK(response_json.isMember("target")) << "Build was missing target field.";
-  return response_json["target"]["product"].asString();
-}
-
-std::vector<Artifact> BuildApi::Artifacts(const DeviceBuild& build) {
-  std::string page_token = "";
-  std::vector<Artifact> artifacts;
-  do {
-    std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target +
-                      "/attempts/latest/artifacts?maxResults=1000";
-    if (page_token != "") {
-      url += "&pageToken=" + page_token;
-    }
-    auto artifacts_json = curl.DownloadToJson(url, Headers());
-    CHECK(!artifacts_json.isMember("error"))
-        << "Error fetching the artifacts of " << build << ". Response was "
-        << artifacts_json;
-    if (artifacts_json.isMember("nextPageToken")) {
-      page_token = artifacts_json["nextPageToken"].asString();
-    } else {
-      page_token = "";
-    }
-    for (const auto& artifact_json : artifacts_json["artifacts"]) {
-      artifacts.emplace_back(artifact_json);
-    }
-  } while (page_token != "");
-  return artifacts;
-}
-
-struct CloseDir {
-  void operator()(DIR* dir) {
-    closedir(dir);
-  }
-};
-
-using UniqueDir = std::unique_ptr<DIR, CloseDir>;
-
-std::vector<Artifact> BuildApi::Artifacts(const DirectoryBuild& build) {
-  std::vector<Artifact> artifacts;
-  for (const auto& path : build.paths) {
-    auto dir = UniqueDir(opendir(path.c_str()));
-    CHECK(dir != nullptr) << "Could not read files from \"" << path << "\"";
-    for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
-      artifacts.emplace_back(std::string(entity->d_name));
-    }
-  }
-  return artifacts;
-}
-
-bool BuildApi::ArtifactToFile(const DeviceBuild& build,
-                              const std::string& artifact,
-                              const std::string& path) {
-  std::string download_url_endpoint =
-      BUILD_API + "/builds/" + build.id + "/" + build.target +
-      "/attempts/latest/artifacts/" + artifact + "/url";
-  auto download_url_json =
-      curl.DownloadToJson(download_url_endpoint, Headers());
-  if (!download_url_json.isMember("signedUrl")) {
-    LOG(ERROR) << "URL endpoint did not have json path: " << download_url_json;
-    return false;
-  }
-  std::string url = download_url_json["signedUrl"].asString();
-  return curl.DownloadToFile(url, path);
-}
-
-bool BuildApi::ArtifactToFile(const DirectoryBuild& build,
-                              const std::string& artifact,
-                              const std::string& destination) {
-  for (const auto& path : build.paths) {
-    auto source = path + "/" + artifact;
-    if (!FileExists(source)) {
-      continue;
-    }
-    unlink(destination.c_str());
-    if (symlink(source.c_str(), destination.c_str())) {
-      int error_num = errno;
-      LOG(ERROR) << "Could not create symlink from " << source << " to "
-                  << destination << ": " << strerror(error_num);
-      return false;
-    }
-    return true;
-  }
-  return false;
-}
-
-Build ArgumentToBuild(BuildApi* build_api, const std::string& arg,
-                      const std::string& default_build_target,
-                      const std::chrono::seconds& retry_period) {
-  if (arg.find(':') != std::string::npos) {
-    std::vector<std::string> dirs = android::base::Split(arg, ":");
-    std::string id = dirs.back();
-    dirs.pop_back();
-    return DirectoryBuild(dirs, id);
-  }
-  size_t slash_pos = arg.find('/');
-  if (slash_pos != std::string::npos
-        && arg.find('/', slash_pos + 1) != std::string::npos) {
-    LOG(FATAL) << "Build argument cannot have more than one '/' slash. Was at "
-        << slash_pos << " and " << arg.find('/', slash_pos + 1);
-  }
-  std::string build_target = slash_pos == std::string::npos
-      ? default_build_target : arg.substr(slash_pos + 1);
-  std::string branch_or_id = slash_pos == std::string::npos
-      ? arg: arg.substr(0, slash_pos);
-  std::string branch_latest_build_id =
-      build_api->LatestBuildId(branch_or_id, build_target);
-  std::string build_id = branch_or_id;
-  if (branch_latest_build_id != "") {
-    LOG(INFO) << "The latest good build on branch \"" << branch_or_id
-        << "\"with build target \"" << build_target
-        << "\" is \"" << branch_latest_build_id << "\"";
-    build_id = branch_latest_build_id;
-  }
-  DeviceBuild proposed_build = DeviceBuild(build_id, build_target);
-  std::string status = build_api->BuildStatus(proposed_build);
-  if (status == "") {
-    LOG(FATAL) << proposed_build << " is not a valid branch or build id.";
-  }
-  LOG(INFO) << "Status for build " << proposed_build << " is " << status;
-  while (retry_period != std::chrono::seconds::zero() && !StatusIsTerminal(status)) {
-    LOG(INFO) << "Status is \"" << status << "\". Waiting for " << retry_period.count()
-        << " seconds.";
-    std::this_thread::sleep_for(retry_period);
-    status = build_api->BuildStatus(proposed_build);
-  }
-  LOG(INFO) << "Status for build " << proposed_build << " is " << status;
-  proposed_build.product = build_api->ProductName(proposed_build);
-  return proposed_build;
-}
-
-} // namespace cuttlefish
diff --git a/host/commands/fetcher/credential_source.cc b/host/commands/fetcher/credential_source.cc
deleted file mode 100644
index 0144e2d..0000000
--- a/host/commands/fetcher/credential_source.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "credential_source.h"
-
-#include <android-base/logging.h>
-
-namespace cuttlefish {
-namespace {
-
-std::chrono::steady_clock::duration REFRESH_WINDOW =
-    std::chrono::minutes(2);
-std::string REFRESH_URL = "http://metadata.google.internal/computeMetadata/"
-    "v1/instance/service-accounts/default/token";
-
-} // namespace
-
-GceMetadataCredentialSource::GceMetadataCredentialSource() {
-  latest_credential = "";
-  expiration = std::chrono::steady_clock::now();
-}
-
-std::string GceMetadataCredentialSource::Credential() {
-  if (expiration - std::chrono::steady_clock::now() < REFRESH_WINDOW) {
-    RefreshCredential();
-  }
-  return latest_credential;
-}
-
-void GceMetadataCredentialSource::RefreshCredential() {
-  Json::Value credential_json =
-      curl.DownloadToJson(REFRESH_URL, {"Metadata-Flavor: Google"});
-
-  CHECK(!credential_json.isMember("error")) << "Error fetching credentials. " <<
-      "Response was " << credential_json;
-  bool has_access_token = credential_json.isMember("access_token");
-  bool has_expires_in = credential_json.isMember("expires_in");
-  if (!has_access_token || !has_expires_in) {
-    LOG(FATAL) << "GCE credential was missing access_token or expires_in. "
-        << "Full response was " << credential_json << "";
-  }
-
-  expiration = std::chrono::steady_clock::now()
-      + std::chrono::seconds(credential_json["expires_in"].asInt());
-  latest_credential = credential_json["access_token"].asString();
-}
-
-std::unique_ptr<CredentialSource> GceMetadataCredentialSource::make() {
-  return std::unique_ptr<CredentialSource>(new GceMetadataCredentialSource());
-}
-
-FixedCredentialSource::FixedCredentialSource(const std::string& credential) {
-  this->credential = credential;
-}
-
-std::string FixedCredentialSource::Credential() {
-  return credential;
-}
-
-std::unique_ptr<CredentialSource> FixedCredentialSource::make(
-    const std::string& credential) {
-  return std::unique_ptr<CredentialSource>(new FixedCredentialSource(credential));
-}
-
-} // namespace cuttlefish
diff --git a/host/commands/fetcher/credential_source.h b/host/commands/fetcher/credential_source.h
deleted file mode 100644
index 78ec51a..0000000
--- a/host/commands/fetcher/credential_source.h
+++ /dev/null
@@ -1,56 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#pragma once
-
-#include <chrono>
-#include <memory>
-
-#include "curl_wrapper.h"
-
-namespace cuttlefish {
-
-class CredentialSource {
-public:
-  virtual ~CredentialSource() = default;
-  virtual std::string Credential() = 0;
-};
-
-class GceMetadataCredentialSource : public CredentialSource {
-  CurlWrapper curl;
-  std::string latest_credential;
-  std::chrono::steady_clock::time_point expiration;
-
-  void RefreshCredential();
-public:
-  GceMetadataCredentialSource();
-  GceMetadataCredentialSource(GceMetadataCredentialSource&&) = default;
-
-  virtual std::string Credential();
-
-  static std::unique_ptr<CredentialSource> make();
-};
-
-class FixedCredentialSource : public CredentialSource {
-  std::string credential;
-public:
-  FixedCredentialSource(const std::string& credential);
-
-  virtual std::string Credential();
-
-  static std::unique_ptr<CredentialSource> make(const std::string& credential);
-};
-
-}
diff --git a/host/commands/fetcher/curl_wrapper.cc b/host/commands/fetcher/curl_wrapper.cc
deleted file mode 100644
index c32f4ce..0000000
--- a/host/commands/fetcher/curl_wrapper.cc
+++ /dev/null
@@ -1,161 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "curl_wrapper.h"
-
-#include <sstream>
-#include <string>
-#include <stdio.h>
-
-#include <android-base/logging.h>
-
-#include <curl/curl.h>
-#include <json/json.h>
-
-namespace cuttlefish {
-namespace {
-
-size_t file_write_callback(char *ptr, size_t, size_t nmemb, void *userdata) {
-  std::stringstream* stream = (std::stringstream*) userdata;
-  stream->write(ptr, nmemb);
-  return nmemb;
-}
-
-curl_slist* build_slist(const std::vector<std::string>& strings) {
-  curl_slist* curl_headers = nullptr;
-  for (const auto& str : strings) {
-    curl_slist* temp = curl_slist_append(curl_headers, str.c_str());
-    if (temp == nullptr) {
-      LOG(ERROR) << "curl_slist_append failed to add " << str;
-      if (curl_headers) {
-        curl_slist_free_all(curl_headers);
-        return nullptr;
-      }
-    }
-    curl_headers = temp;
-  }
-  return curl_headers;
-}
-
-} // namespace
-
-CurlWrapper::CurlWrapper() {
-  curl = curl_easy_init();
-  if (!curl) {
-    LOG(ERROR) << "failed to initialize curl";
-    return;
-  }
-}
-
-CurlWrapper::~CurlWrapper() {
-  curl_easy_cleanup(curl);
-}
-
-bool CurlWrapper::DownloadToFile(const std::string& url, const std::string& path) {
-  return CurlWrapper::DownloadToFile(url, path, {});
-}
-
-bool CurlWrapper::DownloadToFile(const std::string& url, const std::string& path,
-                                 const std::vector<std::string>& headers) {
-  LOG(INFO) << "Attempting to save \"" << url << "\" to \"" << path << "\"";
-  if (!curl) {
-    LOG(ERROR) << "curl was not initialized\n";
-    return false;
-  }
-  curl_slist* curl_headers = build_slist(headers);
-  curl_easy_reset(curl);
-  curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt");
-  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers);
-  curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
-  char error_buf[CURL_ERROR_SIZE];
-  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buf);
-  curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
-  FILE* file = fopen(path.c_str(), "w");
-  if (!file) {
-    LOG(ERROR) << "could not open file " << path;
-    return false;
-  }
-  curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*) file);
-  CURLcode res = curl_easy_perform(curl);
-  if (curl_headers) {
-    curl_slist_free_all(curl_headers);
-  }
-  fclose(file);
-  if (res != CURLE_OK) {
-    LOG(ERROR) << "curl_easy_perform() failed. "
-        << "Code was \"" << res << "\". "
-        << "Strerror was \"" << curl_easy_strerror(res) << "\". "
-        << "Error buffer was \"" << error_buf << "\".";
-    return false;
-  }
-  return true;
-}
-
-std::string CurlWrapper::DownloadToString(const std::string& url) {
-  return DownloadToString(url, {});
-}
-
-std::string CurlWrapper::DownloadToString(const std::string& url,
-                                          const std::vector<std::string>& headers) {
-  LOG(INFO) << "Attempting to download \"" << url << "\"";
-  if (!curl) {
-    LOG(ERROR) << "curl was not initialized\n";
-    return "";
-  }
-  curl_slist* curl_headers = build_slist(headers);
-  curl_easy_reset(curl);
-  curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt");
-  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers);
-  curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
-  std::stringstream data;
-  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, file_write_callback);
-  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
-  char error_buf[CURL_ERROR_SIZE];
-  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buf);
-  curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
-  CURLcode res = curl_easy_perform(curl);
-  if (curl_headers) {
-    curl_slist_free_all(curl_headers);
-  }
-  if (res != CURLE_OK) {
-    LOG(ERROR) << "curl_easy_perform() failed. "
-        << "Code was \"" << res << "\". "
-        << "Strerror was \"" << curl_easy_strerror(res) << "\". "
-        << "Error buffer was \"" << error_buf << "\".";
-    return "";
-  }
-  return data.str();
-}
-
-Json::Value CurlWrapper::DownloadToJson(const std::string& url) {
-  return DownloadToJson(url, {});
-}
-
-Json::Value CurlWrapper::DownloadToJson(const std::string& url,
-                                        const std::vector<std::string>& headers) {
-  std::string contents = DownloadToString(url, headers);
-  Json::CharReaderBuilder builder;
-  std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
-  Json::Value json;
-  std::string errorMessage;
-  if (!reader->parse(&*contents.begin(), &*contents.end(), &json, &errorMessage)) {
-    LOG(ERROR) << "Could not parse json: " << errorMessage;
-    json["error"] = "Failed to parse json.";
-    json["response"] = contents;
-  }
-  return json;
-}
-
-}
diff --git a/host/commands/fetcher/curl_wrapper.h b/host/commands/fetcher/curl_wrapper.h
deleted file mode 100644
index 6d3a2cb..0000000
--- a/host/commands/fetcher/curl_wrapper.h
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#pragma once
-
-#include <string>
-
-#include <curl/curl.h>
-#include <json/json.h>
-
-namespace cuttlefish {
-
-class CurlWrapper {
-  CURL* curl;
-public:
-  CurlWrapper();
-  ~CurlWrapper();
-  CurlWrapper(const CurlWrapper&) = delete;
-  CurlWrapper& operator=(const CurlWrapper*) = delete;
-  CurlWrapper(CurlWrapper&&) = default;
-
-  bool DownloadToFile(const std::string& url, const std::string& path);
-  bool DownloadToFile(const std::string& url, const std::string& path,
-                      const std::vector<std::string>& headers);
-  std::string DownloadToString(const std::string& url);
-  std::string DownloadToString(const std::string& url,
-                               const std::vector<std::string>& headers);
-  Json::Value DownloadToJson(const std::string& url);
-  Json::Value DownloadToJson(const std::string& url,
-                             const std::vector<std::string>& headers);
-};
-
-}
diff --git a/host/commands/fetcher/fetch_cvd.cc b/host/commands/fetcher/fetch_cvd.cc
index b84fbba..2154233 100644
--- a/host/commands/fetcher/fetch_cvd.cc
+++ b/host/commands/fetcher/fetch_cvd.cc
@@ -13,10 +13,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <fstream>
 #include <iostream>
 #include <iterator>
 #include <string>
 
+#include <curl/curl.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
@@ -26,14 +28,15 @@
 
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/archive.h"
+#include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
 #include "common/libs/utils/subprocess.h"
 
 #include "host/libs/config/fetcher_config.h"
 
-#include "build_api.h"
-#include "credential_source.h"
-#include "install_zip.h"
+#include "host/libs/web/build_api.h"
+#include "host/libs/web/credential_source.h"
+#include "host/libs/web/install_zip.h"
 
 namespace {
 
@@ -43,6 +46,7 @@
 
 using cuttlefish::CurrentDirectory;
 
+DEFINE_string(api_key, "", "API key for the Android Build API");
 DEFINE_string(default_build, DEFAULT_BRANCH + "/" + DEFAULT_BUILD_TARGET,
               "source for the cuttlefish build to use (vendor.img + host)");
 DEFINE_string(system_build, "", "source for system.img and product.img");
@@ -78,9 +82,9 @@
     const std::vector<Artifact>& artifacts) {
   std::string product = std::visit([](auto&& arg) { return arg.product; }, build);
   auto id = std::visit([](auto&& arg) { return arg.id; }, build);
-  auto match = product + "-" + name + "-" + id;
+  auto match = product + "-" + name + "-" + id + ".zip";
   for (const auto& artifact : artifacts) {
-    if (artifact.Name().find(match) != std::string::npos) {
+    if (artifact.Name() == match) {
       return artifact.Name();
     }
   }
@@ -249,6 +253,31 @@
     "\"branch\" - latest build of \"branch\" for \"aosp_cf_x86_phone-userdebug\"\n"
     "\"build_id\" - build \"build_id\" for \"aosp_cf_x86_phone-userdebug\"\n";
 
+std::unique_ptr<CredentialSource> TryOpenServiceAccountFile(
+    CurlWrapper& curl, const std::string& path) {
+  LOG(VERBOSE) << "Attempting to open service account file \"" << path << "\"";
+  Json::CharReaderBuilder builder;
+  std::ifstream ifs(path);
+  Json::Value content;
+  std::string errorMessage;
+  if (!Json::parseFromStream(builder, ifs, &content, &errorMessage)) {
+    LOG(VERBOSE) << "Could not read config file \"" << path
+                 << "\": " << errorMessage;
+    return {};
+  }
+  static constexpr char BUILD_SCOPE[] =
+      "https://www.googleapis.com/auth/androidbuild.internal";
+  auto result =
+      ServiceAccountOauthCredentialSource::FromJson(curl, content, BUILD_SCOPE);
+  if (!result.ok()) {
+    LOG(VERBOSE) << "Failed to load service account json file: \n"
+                 << result.error();
+    return {};
+  }
+  return std::unique_ptr<CredentialSource>(
+      new ServiceAccountOauthCredentialSource(std::move(*result)));
+}
+
 } // namespace
 
 int FetchCvdMain(int argc, char** argv) {
@@ -268,13 +297,35 @@
 
   curl_global_init(CURL_GLOBAL_DEFAULT);
   {
+    auto curl = CurlWrapper::Create();
+    auto retrying_curl = CurlWrapper::WithServerErrorRetry(
+        *curl, 10, std::chrono::milliseconds(5000));
     std::unique_ptr<CredentialSource> credential_source;
-    if (FLAGS_credential_source == "gce") {
-      credential_source = GceMetadataCredentialSource::make();
-    } else if (FLAGS_credential_source != "") {
+    if (auto crds = TryOpenServiceAccountFile(*curl, FLAGS_credential_source)) {
+      credential_source = std::move(crds);
+    } else if (FLAGS_credential_source == "gce") {
+      credential_source = GceMetadataCredentialSource::make(*retrying_curl);
+    } else if (FLAGS_credential_source == "") {
+      std::string file = StringFromEnv("HOME", ".") + "/.acloud_oauth2.dat";
+      LOG(VERBOSE) << "Probing acloud credentials at " << file;
+      if (FileExists(file)) {
+        std::ifstream stream(file);
+        auto attempt_load =
+            RefreshCredentialSource::FromOauth2ClientFile(*curl, stream);
+        if (attempt_load.ok()) {
+          credential_source.reset(
+              new RefreshCredentialSource(std::move(*attempt_load)));
+        } else {
+          LOG(VERBOSE) << "Failed to load acloud credentials: "
+                       << attempt_load.error();
+        }
+      } else {
+        LOG(INFO) << "\"" << file << "\" missing, running without credentials";
+      }
+    } else {
       credential_source = FixedCredentialSource::make(FLAGS_credential_source);
     }
-    BuildApi build_api(std::move(credential_source));
+    BuildApi build_api(*retrying_curl, credential_source.get(), FLAGS_api_key);
 
     auto default_build = ArgumentToBuild(&build_api, FLAGS_default_build,
                                          DEFAULT_BUILD_TARGET,
@@ -293,6 +344,9 @@
       if (FLAGS_otatools_build != "") {
         ota_build = ArgumentToBuild(&build_api, FLAGS_otatools_build,
                                     DEFAULT_BUILD_TARGET, retry_period);
+      } else if (FLAGS_system_build != "") {
+        ota_build = ArgumentToBuild(&build_api, FLAGS_system_build,
+                                    DEFAULT_BUILD_TARGET, retry_period);
       }
       std::vector<std::string> ota_tools_files =
           download_ota_tools(&build_api, ota_build, target_dir);
diff --git a/host/commands/gnss_grpc_proxy/Android.bp b/host/commands/gnss_grpc_proxy/Android.bp
index e85a919..f490238 100644
--- a/host/commands/gnss_grpc_proxy/Android.bp
+++ b/host/commands/gnss_grpc_proxy/Android.bp
@@ -17,22 +17,10 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-cc_binary {
-    name: "gnss_grpc_proxy",
-    srcs: [
-        "gnss_grpc_proxy.cpp",
-    ],
-    cflags: [
-        "-Wno-unused-parameter",
-        "-D_XOPEN_SOURCE",
-    ],
-    generated_headers: [
-        "GnssGrpcProxyStub_h",
-    ],
-    generated_sources: [
-        "GnssGrpcProxyStub_cc",
-    ],
+cc_library_static {
+    name: "libcvd_gnss_grpc_proxy",
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
@@ -44,6 +32,19 @@
         "libcuttlefish_host_config",
         "libgflags",
     ],
+    cflags: [
+        "-Wno-unused-parameter",
+        "-D_XOPEN_SOURCE",
+    ],
+    generated_headers: [
+        "GnssGrpcProxyStub_h",
+    ],
+    generated_sources: [
+        "GnssGrpcProxyStub_cc",
+    ],
+    export_generated_headers: [
+        "GnssGrpcProxyStub_h",
+    ],
     defaults: ["cuttlefish_host"],
     include_dirs: [
         "external/grpc-grpc/include",
@@ -51,6 +52,32 @@
     ],
 }
 
+cc_binary {
+    name: "gnss_grpc_proxy",
+    shared_libs: [
+        "libext2_blkid",
+        "libbase",
+        "libcuttlefish_fs",
+        "libcuttlefish_utils",
+        "libjsoncpp",
+        "libprotobuf-cpp-full",
+        "libgrpc++_unsecure",
+    ],
+    static_libs: [
+        "libcuttlefish_host_config",
+        "libgflags",
+        "libcvd_gnss_grpc_proxy",
+    ],
+    srcs: [
+        "gnss_grpc_proxy.cpp",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+        "-D_XOPEN_SOURCE",
+    ],
+    defaults: ["cuttlefish_host"],
+}
+
 filegroup {
     name: "GnssGrpcProxyProto",
     srcs: [
diff --git a/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.cpp b/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.cpp
index ff8e54e..c5ecc44 100644
--- a/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.cpp
+++ b/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.cpp
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 
-#include <iostream>
+#include <chrono>
+#include <ctime>
 #include <fstream>
+#include <iostream>
 #include <memory>
 #include <string>
 
@@ -28,11 +30,13 @@
 #include <chrono>
 #include <deque>
 #include <mutex>
+#include <sstream>
 #include <thread>
 #include <vector>
 
-#include <gflags/gflags.h>
 #include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <gflags/gflags.h>
 
 #include <common/libs/fs/shared_fd.h>
 #include <common/libs/fs/shared_buf.h>
@@ -63,6 +67,9 @@
               "NMEA file path for gnss grpc");
 
 constexpr char CMD_GET_LOCATION[] = "CMD_GET_LOCATION";
+constexpr char CMD_GET_RAWMEASUREMENT[] = "CMD_GET_RAWMEASUREMENT";
+constexpr char END_OF_MSG_MARK[] = "\n\n\n\n";
+
 constexpr uint32_t GNSS_SERIAL_BUFFER_SIZE = 4096;
 // Logic and data behind the server's behavior.
 class GnssGrpcProxyServiceImpl final : public GnssGrpcProxy::Service {
@@ -73,7 +80,6 @@
     Status SendNmea(ServerContext* context, const SendNmeaRequest* request,
                     SendNmeaReply* reply) override {
       reply->set_reply("Received nmea record.");
-
       auto buffer = request->nmea();
       std::lock_guard<std::mutex> lock(cached_nmea_mutex);
       cached_nmea = request->nmea();
@@ -81,24 +87,54 @@
     }
 
     void sendToSerial() {
-      LOG(DEBUG) << "Send NMEA to serial:" << cached_nmea;
       std::lock_guard<std::mutex> lock(cached_nmea_mutex);
-      ssize_t bytes_written = cuttlefish::WriteAll(gnss_in_, cached_nmea);
+      if (!isNMEA(cached_nmea)) {
+        return;
+      }
+      ssize_t bytes_written =
+          cuttlefish::WriteAll(gnss_in_, cached_nmea + END_OF_MSG_MARK);
       if (bytes_written < 0) {
           LOG(ERROR) << "Error writing to fd: " << gnss_in_->StrError();
       }
     }
 
+    void sendGnssRawToSerial() {
+      std::lock_guard<std::mutex> lock(cached_gnss_raw_mutex);
+      if (!isGnssRawMeasurement(cached_gnss_raw)) {
+        return;
+      }
+      if (previous_cached_gnss_raw == cached_gnss_raw) {
+        // Skip for same record
+        return;
+      } else {
+        // Update cached data
+        LOG(DEBUG) << "Skip same record";
+        previous_cached_gnss_raw = cached_gnss_raw;
+      }
+      ssize_t bytes_written =
+          cuttlefish::WriteAll(gnss_in_, cached_gnss_raw + END_OF_MSG_MARK);
+      LOG(DEBUG) << "Send Gnss Raw to serial: bytes_written: " << bytes_written;
+      if (bytes_written < 0) {
+        LOG(ERROR) << "Error writing to fd: " << gnss_in_->StrError();
+      }
+    }
+
     void StartServer() {
       // Create a new thread to handle writes to the gnss and to the any client
       // connected to the socket.
       read_thread_ = std::thread([this]() { ReadLoop(); });
     }
 
-    void StartReadFileThread() {
-      // Create a new thread to handle writes to the gnss and to the any client
-      // connected to the socket.
-      file_read_thread_ = std::thread([this]() { ReadNmeaFromLocalFile(); });
+    void StartReadNmeaFileThread() {
+      // Create a new thread to read nmea data.
+      nmea_file_read_thread_ =
+          std::thread([this]() { ReadNmeaFromLocalFile(); });
+    }
+
+    void StartReadGnssRawMeasurementFileThread() {
+      // Create a new thread to read raw measurement data.
+      measurement_file_read_thread_ =
+          std::thread([this]() { ReadGnssRawMeasurement(); });
     }
 
     void ReadNmeaFromLocalFile() {
@@ -134,6 +170,70 @@
         return;
       }
     }
+
+    void ReadGnssRawMeasurement() {
+      std::ifstream file(FLAGS_gnss_file_path);
+
+      if (file.is_open()) {
+        std::string line;
+        std::string cached_line = "";
+        std::string header = "";
+
+        while (!cached_line.empty() || std::getline(file, line)) {
+          if (!cached_line.empty()) {
+            line = cached_line;
+            cached_line = "";
+          }
+
+          // Get data header.
+          if (header.empty() && android::base::StartsWith(line, "# Raw")) {
+            header = line;
+            LOG(DEBUG) << "Header: " << header;
+            continue;
+          }
+
+          // Ignore not raw measurement data.
+          if (!android::base::StartsWith(line, "Raw")) {
+            continue;
+          }
+
+          {
+            std::lock_guard<std::mutex> lock(cached_gnss_raw_mutex);
+            cached_gnss_raw = header + "\n" + line;
+
+            std::string new_line = "";
+            while (std::getline(file, new_line)) {
+              // Group raw data by TimeNanos.
+              if (getTimeNanosFromLine(new_line) ==
+                  getTimeNanosFromLine(line)) {
+                cached_gnss_raw += "\n" + new_line;
+              } else {
+                cached_line = new_line;
+                break;
+              }
+            }
+          }
+          std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+        }
+        file.close();
+      } else {
+        LOG(ERROR) << "Can not open GNSS Raw file: " << FLAGS_gnss_file_path;
+        return;
+      }
+    }
+
+    ~GnssGrpcProxyServiceImpl() {
+      if (nmea_file_read_thread_.joinable()) {
+        nmea_file_read_thread_.join();
+      }
+      if (measurement_file_read_thread_.joinable()) {
+        measurement_file_read_thread_.join();
+      }
+      if (read_thread_.joinable()) {
+        read_thread_.join();
+      }
+    }
+
   private:
     [[noreturn]] void ReadLoop() {
       cuttlefish::SharedFDSet read_set;
@@ -159,6 +259,12 @@
             gnss_cmd_str = "";
             total_read = 0;
           }
+
+          if (gnss_cmd_str.find(CMD_GET_RAWMEASUREMENT) != std::string::npos) {
+            sendGnssRawToSerial();
+            gnss_cmd_str = "";
+            total_read = 0;
+          }
         } else {
           if (gnss_out_->GetErrno() == EAGAIN|| gnss_out_->GetErrno() == EWOULDBLOCK) {
             std::this_thread::sleep_for(std::chrono::milliseconds(100));
@@ -171,12 +277,35 @@
       }
     }
 
+    std::string getTimeNanosFromLine(const std::string& line) {
+      // TimeNanos is in column #3.
+      std::vector<std::string> vals = android::base::Split(line, ",");
+      return vals.size() >= 3 ? vals[2] : "-1";
+    }
+
+    bool isGnssRawMeasurement(const std::string& inputStr) {
+      // TODO: add more logic check to by pass invalid data.
+      return !inputStr.empty() && android::base::StartsWith(inputStr, "# Raw");
+    }
+
+    bool isNMEA(const std::string& inputStr) {
+      return !inputStr.empty() &&
+             (android::base::StartsWith(inputStr, "$GPRMC") ||
+              android::base::StartsWith(inputStr, "$GPRMA"));
+    }
+
     cuttlefish::SharedFD gnss_in_;
     cuttlefish::SharedFD gnss_out_;
     std::thread read_thread_;
-    std::thread file_read_thread_;
+    std::thread nmea_file_read_thread_;
+    std::thread measurement_file_read_thread_;
+
     std::string cached_nmea;
     std::mutex cached_nmea_mutex;
+
+    std::string cached_gnss_raw;
+    std::string previous_cached_gnss_raw;
+    std::mutex cached_gnss_raw_mutex;
 };
 
 void RunServer() {
@@ -200,7 +329,10 @@
   GnssGrpcProxyServiceImpl service(gnss_in, gnss_out);
   service.StartServer();
   if (!FLAGS_gnss_file_path.empty()) {
-    service.StartReadFileThread();
+    // TODO: On-demand start the read file threads according to data type.
+    service.StartReadNmeaFileThread();
+    service.StartReadGnssRawMeasurementFileThread();
+
     // In the local mode, we are not start a grpc server, use a infinite loop instead
     while(true) {
       std::this_thread::sleep_for(std::chrono::milliseconds(2000));
@@ -232,4 +364,4 @@
   RunServer();
 
   return 0;
-}
+}
\ No newline at end of file
diff --git a/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.proto b/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.proto
index 2a12d2d..4bfc854 100644
--- a/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.proto
+++ b/host/commands/gnss_grpc_proxy/gnss_grpc_proxy.proto
@@ -2,6 +2,9 @@
 
 package gnss_grpc_proxy;
 
+option java_multiple_files = true;
+option java_package = "com.android.cuttlefish.gnssproxy.proto";
+
 // The greeting service definition.
 service GnssGrpcProxy {
   // Sends NmeaRequest
diff --git a/host/commands/stop_cvd/Android.bp b/host/commands/health/Android.bp
similarity index 87%
copy from host/commands/stop_cvd/Android.bp
copy to host/commands/health/Android.bp
index a670a25..3681332 100644
--- a/host/commands/stop_cvd/Android.bp
+++ b/host/commands/health/Android.bp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2018 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -18,15 +18,16 @@
 }
 
 cc_binary {
-    name: "stop_cvd",
+    name: "health",
     srcs: [
-        "main.cc",
+        "health.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
-        "libcuttlefish_allocd_utils",
+        "libfruit",
         "libjsoncpp",
     ],
     static_libs: [
diff --git a/host/commands/health/health.cpp b/host/commands/health/health.cpp
new file mode 100644
index 0000000..37cce86
--- /dev/null
+++ b/host/commands/health/health.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <iostream>
+//
+#include <android-base/logging.h>
+#include <gflags/gflags.h>
+//
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/vm_manager/vm_manager.h"
+
+std::string GetControlSocketPath(const cuttlefish::CuttlefishConfig& config) {
+  return config.ForDefaultInstance().PerInstanceInternalPath(
+      "crosvm_control.sock");
+}
+
+std::string USAGE_MESSAGE =
+    "<key> [value]\n"
+    "Excluding the value will enumerate the possible values to set\n"
+    "\n"
+    "\"status [value]\" - battery status: "
+    "unknown/charging/discharging/notcharging/full\n"
+    "\"health [value]\" - battery health\n"
+    "\"present [value]\" - battery present: 1 or 0\n"
+    "\"capacity [value]\" - battery capacity: 0 to 100\n"
+    "\"aconline [value]\" - battery ac online: 1 or 0\n";
+
+int status() {
+  std::cout
+      << "health status [value]\n"
+         "\"value\" - unknown, charging, discharging, notcharging, full\n";
+  return 0;
+}
+
+int health() {
+  std::cout << "health health [value]\n"
+               "\"value\" - unknown, good, overheat, dead, overvoltage, "
+               "unexpectedfailure,\n"
+               "          cold, watchdogtimerexpire, safetytimerexpire, "
+               "overcurrent\n";
+  return 0;
+}
+
+int present() {
+  std::cout << "health present [value]\n"
+               "\"value\" - 1, 0\n";
+  return 0;
+}
+
+int capacity() {
+  std::cout << "health capacity [value]\n"
+               "\"value\" - 0 to 100\n";
+  return 0;
+}
+
+int aconline() {
+  std::cout << "health aconline [value]\n"
+               "\"value\" - 1, 0\n";
+  return 0;
+}
+
+int usage() {
+  std::cout << "health " << USAGE_MESSAGE;
+  return 1;
+}
+
+int main(int argc, char** argv) {
+  ::android::base::InitLogging(argv, android::base::StderrLogger);
+  gflags::SetUsageMessage(USAGE_MESSAGE);
+
+  auto config = cuttlefish::CuttlefishConfig::Get();
+  if (!config) {
+    LOG(ERROR) << "Failed to obtain config object";
+    return 1;
+  }
+
+  if (argc != 2 && argc != 3) {
+    return usage();
+  }
+
+  std::string key = argv[1];
+  std::string value = "";
+  if (argc == 3) {
+    value = argv[2];
+  }
+
+  if (argc == 2 || value == "--help" || value == "-h" || value == "help") {
+    if (key == "status") {
+      return status();
+    } else if (key == "health") {
+      return health();
+    } else if (key == "present") {
+      return present();
+    } else if (key == "capacity") {
+      return capacity();
+    } else if (key == "aconline") {
+      return aconline();
+    } else {
+      return usage();
+    }
+  }
+
+  cuttlefish::Command command(config->crosvm_binary());
+  command.AddParameter("battery");
+  command.AddParameter("goldfish");
+  command.AddParameter(key);
+  command.AddParameter(value);
+  command.AddParameter(GetControlSocketPath(*config));
+
+  std::string output, error;
+  auto ret = RunWithManagedStdio(std::move(command), NULL, &output, &error);
+  if (ret != 0) {
+    LOG(ERROR) << "goldfish battery returned: " << ret << "\n" << output << "\n" << error;
+  }
+  return ret;
+}
diff --git a/host/commands/cvd_host_bugreport/Android.bp b/host/commands/host_bugreport/Android.bp
similarity index 89%
rename from host/commands/cvd_host_bugreport/Android.bp
rename to host/commands/host_bugreport/Android.bp
index 4f6aeda..76f184b 100644
--- a/host/commands/cvd_host_bugreport/Android.bp
+++ b/host/commands/host_bugreport/Android.bp
@@ -18,14 +18,17 @@
 }
 
 cc_binary {
-    name: "cvd_host_bugreport",
+    name: "cvd_internal_host_bugreport",
+    symlinks: ["cvd_host_bugreport"],
     srcs: [
         "main.cc",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
+        "libfruit",
         "libjsoncpp",
         "libziparchive",
     ],
diff --git a/host/commands/cvd_host_bugreport/main.cc b/host/commands/host_bugreport/main.cc
similarity index 100%
rename from host/commands/cvd_host_bugreport/main.cc
rename to host/commands/host_bugreport/main.cc
diff --git a/host/commands/kernel_log_monitor/Android.bp b/host/commands/kernel_log_monitor/Android.bp
index 02e2be8..7421115 100644
--- a/host/commands/kernel_log_monitor/Android.bp
+++ b/host/commands/kernel_log_monitor/Android.bp
@@ -24,6 +24,7 @@
         "kernel_log_server.cc",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libcuttlefish_kernel_log_monitor_utils",
diff --git a/host/commands/kernel_log_monitor/kernel_log_server.cc b/host/commands/kernel_log_monitor/kernel_log_server.cc
index 03fc90d..fe3a588 100644
--- a/host/commands/kernel_log_monitor/kernel_log_server.cc
+++ b/host/commands/kernel_log_monitor/kernel_log_server.cc
@@ -16,7 +16,8 @@
 
 #include "host/commands/kernel_log_monitor/kernel_log_server.h"
 
-#include <map>
+#include <string>
+#include <tuple>
 #include <utility>
 
 #include <android-base/logging.h>
@@ -25,29 +26,45 @@
 #include "common/libs/fs/shared_select.h"
 #include "host/libs/config/cuttlefish_config.h"
 
-using cuttlefish::SharedFD;
-
 namespace {
-static const std::map<std::string, std::string> kInformationalPatterns = {
+
+using cuttlefish::SharedFD;
+using monitor::Event;
+
+constexpr struct {
+  std::string_view match;   // Substring to match in the kernel logs
+  std::string_view prefix;  // Prefix value to output, describing the entry
+} kInformationalPatterns[] = {
     {"U-Boot ", "GUEST_UBOOT_VERSION: "},
     {"] Linux version ", "GUEST_KERNEL_VERSION: "},
     {"GUEST_BUILD_FINGERPRINT: ", "GUEST_BUILD_FINGERPRINT: "},
 };
 
-static const std::map<std::string, monitor::Event> kStageToEventMap = {
-    {cuttlefish::kBootStartedMessage, monitor::Event::BootStarted},
-    {cuttlefish::kBootCompletedMessage, monitor::Event::BootCompleted},
-    {cuttlefish::kBootFailedMessage, monitor::Event::BootFailed},
-    {cuttlefish::kMobileNetworkConnectedMessage,
-     monitor::Event::MobileNetworkConnected},
-    {cuttlefish::kWifiConnectedMessage, monitor::Event::WifiNetworkConnected},
-    {cuttlefish::kEthernetConnectedMessage,
-     monitor::Event::EthernetNetworkConnected},
+enum EventFormat {
+  kBare,          // Just an event, no extra data
+  kKeyValuePair,  // <stage> <key>=<value>
+};
+
+constexpr struct {
+  std::string_view stage;  // substring in the log identifying the stage
+  Event event;             // emitted when the stage is encountered
+  EventFormat format;      // how the log message is formatted
+} kStageTable[] = {
+    {cuttlefish::kBootStartedMessage, Event::BootStarted, kBare},
+    {cuttlefish::kBootCompletedMessage, Event::BootCompleted, kBare},
+    {cuttlefish::kBootFailedMessage, Event::BootFailed, kKeyValuePair},
+    {cuttlefish::kMobileNetworkConnectedMessage, Event::MobileNetworkConnected,
+     kBare},
+    {cuttlefish::kWifiConnectedMessage, Event::WifiNetworkConnected, kBare},
+    {cuttlefish::kEthernetConnectedMessage, Event::EthernetNetworkConnected,
+     kBare},
     // TODO(b/131864854): Replace this with a string less likely to change
-    {"init: starting service 'adbd'...", monitor::Event::AdbdStarted},
-    {cuttlefish::kScreenChangedMessage, monitor::Event::ScreenChanged},
+    {"init: starting service 'adbd'...", Event::AdbdStarted, kBare},
+    {cuttlefish::kScreenChangedMessage, Event::ScreenChanged, kKeyValuePair},
+    {cuttlefish::kBootloaderLoadedMessage, Event::BootloaderLoaded, kBare},
+    {cuttlefish::kKernelLoadedMessage, Event::KernelLoaded, kBare},
     {cuttlefish::kDisplayPowerModeChangedMessage,
-     monitor::Event::DisplayPowerModeChanged},
+     monitor::Event::DisplayPowerModeChanged, kKeyValuePair},
 };
 
 void ProcessSubscriptions(
@@ -112,17 +129,13 @@
   // Detect VIRTUAL_DEVICE_BOOT_*
   for (ssize_t i=0; i<ret; i++) {
     if ('\n' == buf[i]) {
-      for (auto& info_kv : kInformationalPatterns) {
-        auto& match = info_kv.first;
-        auto& prefix = info_kv.second;
+      for (auto& [match, prefix] : kInformationalPatterns) {
         auto pos = line_.find(match);
         if (std::string::npos != pos) {
           LOG(INFO) << prefix << line_.substr(pos + match.size());
         }
       }
-      for (auto& stage_kv : kStageToEventMap) {
-        auto& stage = stage_kv.first;
-        auto event = stage_kv.second;
+      for (const auto& [stage, event, format] : kStageTable) {
         auto pos = line_.find(stage);
         if (std::string::npos != pos) {
           // Log the stage
@@ -131,23 +144,26 @@
           Json::Value message;
           message["event"] = event;
           Json::Value metadata;
-          // Expect space-separated key=value pairs in the log message.
-          const auto& fields = android::base::Split(
-              line_.substr(pos + stage.size()), " ");
-          for (std::string field : fields) {
-            field = android::base::Trim(field);
-            if (field.empty()) {
-              // Expected; android::base::Split() always returns at least
-              // one (possibly empty) string.
-              LOG(DEBUG) << "Empty field for line: " << line_;
-              continue;
+
+          if (format == kKeyValuePair) {
+            // Expect space-separated key=value pairs in the log message.
+            const auto& fields =
+                android::base::Split(line_.substr(pos + stage.size()), " ");
+            for (std::string field : fields) {
+              field = android::base::Trim(field);
+              if (field.empty()) {
+                // Expected; android::base::Split() always returns at least
+                // one (possibly empty) string.
+                LOG(DEBUG) << "Empty field for line: " << line_;
+                continue;
+              }
+              const auto& keyvalue = android::base::Split(field, "=");
+              if (keyvalue.size() != 2) {
+                LOG(WARNING) << "Field is not in key=value format: " << field;
+                continue;
+              }
+              metadata[keyvalue[0]] = keyvalue[1];
             }
-            const auto& keyvalue = android::base::Split(field, "=");
-            if (keyvalue.size() != 2) {
-              LOG(WARNING) << "Field is not in key=value format: " << field;
-              continue;
-            }
-            metadata[keyvalue[0]] = keyvalue[1];
           }
           message["metadata"] = metadata;
           ProcessSubscriptions(message, &subscribers_);
@@ -157,7 +173,7 @@
           if (deprecated_boot_completed_) {
             // Write to host kernel log
             FILE* log = popen("/usr/bin/sudo /usr/bin/tee /dev/kmsg", "w");
-            fprintf(log, "%s\n", stage.c_str());
+            fprintf(log, "%s\n", std::string(stage).c_str());
             fclose(log);
           }
         }
diff --git a/host/commands/kernel_log_monitor/kernel_log_server.h b/host/commands/kernel_log_monitor/kernel_log_server.h
index dab675d..754ff24 100644
--- a/host/commands/kernel_log_monitor/kernel_log_server.h
+++ b/host/commands/kernel_log_monitor/kernel_log_server.h
@@ -37,7 +37,11 @@
   AdbdStarted = 5,
   ScreenChanged = 6,
   EthernetNetworkConnected = 7,
-  DisplayPowerModeChanged = 9,
+  KernelLoaded = 8,     // BootStarted actually comes quite late in the boot.
+  BootloaderLoaded = 9, /* BootloaderLoaded is the earliest possible indicator
+                         * that we're booting a device.
+                         */
+  DisplayPowerModeChanged = 10,
 };
 
 enum class SubscriptionAction {
diff --git a/host/commands/kernel_log_monitor/main.cc b/host/commands/kernel_log_monitor/main.cc
index 3f708b7..c7269b0 100644
--- a/host/commands/kernel_log_monitor/main.cc
+++ b/host/commands/kernel_log_monitor/main.cc
@@ -94,7 +94,7 @@
     return 2;
   }
 
-  monitor::KernelLogServer klog{pipe, instance.PerInstancePath("kernel.log"),
+  monitor::KernelLogServer klog{pipe, instance.PerInstanceLogPath("kernel.log"),
                                 config->deprecated_boot_completed()};
 
   for (auto subscriber_fd: subscriber_fds) {
diff --git a/host/commands/log_tee/Android.bp b/host/commands/log_tee/Android.bp
index 49b3e69..ed1f652 100644
--- a/host/commands/log_tee/Android.bp
+++ b/host/commands/log_tee/Android.bp
@@ -23,9 +23,11 @@
         "log_tee.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libbase",
+        "libfruit",
         "libjsoncpp",
         "libnl",
     ],
diff --git a/host/commands/log_tee/log_tee.cpp b/host/commands/log_tee/log_tee.cpp
index 7021a99..b63c2ac 100644
--- a/host/commands/log_tee/log_tee.cpp
+++ b/host/commands/log_tee/log_tee.cpp
@@ -64,7 +64,7 @@
     // There is no guarantee of success all the time since log line boundaries
     // could be out sync with the reads, but that's ok.
     if (android::base::StartsWith(trimmed, "[INFO")) {
-      LOG(INFO) << trimmed;
+      LOG(DEBUG) << trimmed;
     } else if (android::base::StartsWith(trimmed, "[ERROR")) {
       LOG(ERROR) << trimmed;
     } else if (android::base::StartsWith(trimmed, "[WARNING")) {
diff --git a/host/commands/logcat_receiver/Android.bp b/host/commands/logcat_receiver/Android.bp
index 305ab7f..63f5b1b 100644
--- a/host/commands/logcat_receiver/Android.bp
+++ b/host/commands/logcat_receiver/Android.bp
@@ -23,6 +23,7 @@
         "main.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libjsoncpp",
diff --git a/host/commands/metrics/Android.bp b/host/commands/metrics/Android.bp
index 67eb3b0..c454b53 100644
--- a/host/commands/metrics/Android.bp
+++ b/host/commands/metrics/Android.bp
@@ -23,6 +23,7 @@
         "metrics.cc",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libbase",
diff --git a/host/commands/metrics/metrics.cc b/host/commands/metrics/metrics.cc
index 378155c..5fcf58e 100644
--- a/host/commands/metrics/metrics.cc
+++ b/host/commands/metrics/metrics.cc
@@ -32,7 +32,7 @@
   CHECK(config) << "Could not open cuttlefish config";
 
   auto instance = config->ForDefaultInstance();
-  auto metrics_log_path = instance.PerInstancePath("metrics.log");
+  auto metrics_log_path = instance.PerInstanceLogPath("metrics.log");
 
   if (config->run_as_daemon()) {
     android::base::SetLogger(
diff --git a/host/commands/mk_cdisk/mk_cdisk.cc b/host/commands/mk_cdisk/mk_cdisk.cc
deleted file mode 100644
index 028547c..0000000
--- a/host/commands/mk_cdisk/mk_cdisk.cc
+++ /dev/null
@@ -1,146 +0,0 @@
-//
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fstream>
-#include <iostream>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/result.h>
-#include <json/json.h>
-
-#include "common/libs/utils/files.h"
-#include "host/libs/image_aggregator/image_aggregator.h"
-
-using android::base::ErrnoError;
-using android::base::Error;
-using android::base::Result;
-
-using cuttlefish::CreateCompositeDisk;
-using cuttlefish::FileExists;
-using cuttlefish::ImagePartition;
-using cuttlefish::kLinuxFilesystem;
-
-// Returns `append` is appended to the end of filename preserving the extension.
-std::string AppendFileName(const std::string& filename,
-                           const std::string& append) {
-  size_t pos = filename.find_last_of('.');
-  if (pos == std::string::npos) {
-    return filename + append;
-  } else {
-    return filename.substr(0, pos) + append + filename.substr(pos);
-  }
-}
-
-// config JSON schema:
-// {
-//   "partitions": [
-//     {
-//       "label": string,
-//       "path": string,
-//       "writable": bool, // optional. defaults to false.
-//     }
-//   ]
-// }
-
-Result<std::vector<ImagePartition>> LoadConfig(std::istream& in) {
-  std::vector<ImagePartition> partitions;
-
-  Json::CharReaderBuilder builder;
-  Json::Value root;
-  Json::String errs;
-  if (!parseFromStream(builder, in, &root, &errs)) {
-    return Error() << "bad config: " << errs;
-  }
-  for (const Json::Value& part : root["partitions"]) {
-    const std::string label = part["label"].asString();
-    const std::string path = part["path"].asString();
-    const bool writable =
-        part["writable"].asBool();  // default: false (if null)
-
-    if (!FileExists(path)) {
-      return Error() << "bad config: Can't find \'" << path << '\'';
-    }
-    partitions.push_back(
-        ImagePartition{label, path, kLinuxFilesystem, .read_only = !writable});
-  }
-
-  if (partitions.empty()) {
-    return Error() << "bad config: no partitions";
-  }
-  return partitions;
-}
-
-Result<std::vector<ImagePartition>> LoadConfig(const std::string& config_file) {
-  if (config_file == "-") {
-    return LoadConfig(std::cin);
-  } else {
-    std::ifstream in(config_file);
-    if (!in) {
-      return ErrnoError() << "Can't open file \'" << config_file << '\'';
-    }
-    return LoadConfig(in);
-  }
-}
-
-struct CompositeDiskArgs {
-  std::string config_file;
-  std::string output_file;
-};
-
-Result<CompositeDiskArgs> ParseCompositeDiskArgs(int argc, char** argv) {
-  if (argc != 3) {
-    std::cerr << fmt::format(
-        "Usage: {0} <config_file> <output_file>\n"
-        "   or  {0} - <output_file>  (read config from STDIN)\n",
-        argv[0]);
-    return Error() << "missing arguments.";
-  }
-  CompositeDiskArgs args{
-      .config_file = argv[1],
-      .output_file = argv[2],
-  };
-  return args;
-}
-
-Result<void> MakeCompositeDiskMain(int argc, char** argv) {
-  setenv("ANDROID_LOG_TAGS", "*:v", /* overwrite */ 0);
-  ::android::base::InitLogging(argv, android::base::StderrLogger);
-
-  auto args = ParseCompositeDiskArgs(argc, argv);
-  if (!args.ok()) {
-    return args.error();
-  }
-  auto partitions = LoadConfig(args->config_file);
-  if (!partitions.ok()) {
-    return partitions.error();
-  }
-
-  // We need two implicit output paths: GPT header/footer
-  // e.g. out.img will have out-header.img and out-footer.img
-  std::string gpt_header = AppendFileName(args->output_file, "-header");
-  std::string gpt_footer = AppendFileName(args->output_file, "-footer");
-  CreateCompositeDisk(*partitions, gpt_header, gpt_footer, args->output_file);
-  return {};
-}
-
-int main(int argc, char** argv) {
-  auto result = MakeCompositeDiskMain(argc, argv);
-  if (!result.ok()) {
-    LOG(ERROR) << result.error();
-    return EXIT_FAILURE;
-  }
-  return 0;
-}
diff --git a/host/commands/mkenvimage_slim/Android.bp b/host/commands/mkenvimage_slim/Android.bp
new file mode 100644
index 0000000..2733261
--- /dev/null
+++ b/host/commands/mkenvimage_slim/Android.bp
@@ -0,0 +1,48 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "mkenvimage_slim",
+    defaults: ["cuttlefish_host", "cuttlefish_libicuuc"],
+    srcs: [
+        "mkenvimage_slim.cc",
+    ],
+    target: {
+        host: {
+            static_libs: [
+                "libbase",
+                "libcuttlefish_fs",
+                "libcuttlefish_utils",
+                "libgflags",
+                "liblog",
+                "libz",
+            ],
+        },
+        android: {
+            shared_libs: [
+                "libbase",
+                "libcuttlefish_fs",
+                "libcuttlefish_utils",
+                "libgflags",
+                "liblog",
+                "libz",
+            ],
+        },
+    },
+}
diff --git a/host/commands/mkenvimage_slim/mkenvimage_slim.cc b/host/commands/mkenvimage_slim/mkenvimage_slim.cc
new file mode 100644
index 0000000..ebd1b9d
--- /dev/null
+++ b/host/commands/mkenvimage_slim/mkenvimage_slim.cc
@@ -0,0 +1,92 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code here has been inspired by
+// https://github.com/u-boot/u-boot/blob/master/tools/mkenvimage.c The bare
+// minimum amount of functionality for our application is replicated.
+
+#include <iostream>
+
+#include <zlib.h>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <gflags/gflags.h>
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
+
+#define PAD_VALUE (0xff)
+#define CRC_SIZE (sizeof(uint32_t))
+
+// One NULL needed at the end of the env.
+#define NULL_PAD_LENGTH (1)
+
+DEFINE_uint32(env_size, 4096, "file size of resulting env");
+DEFINE_string(output_path, "", "output file path");
+DEFINE_string(input_path, "", "input file path");
+namespace cuttlefish {
+namespace {
+std::string USAGE_MESSAGE =
+    "<flags>\n"
+    "\n"
+    "env_size - length in bytes of the resulting env image. Defaults to 4kb.\n"
+    "input_path - path to input key value mapping as a text file\n"
+    "output_path - path to write resulting environment image including CRC "
+    "to\n";
+}  // namespace
+
+Result<int> MkenvimageSlimMain(int argc, char** argv) {
+  ::android::base::InitLogging(argv, android::base::StderrLogger);
+  gflags::SetUsageMessage(USAGE_MESSAGE);
+  gflags::ParseCommandLineFlags(&argc, &argv, true);
+  CF_EXPECT(FLAGS_output_path != "", "Output env path isn't defined.");
+  CF_EXPECT(FLAGS_env_size != 0, "env size can't be 0.");
+  CF_EXPECT(!(FLAGS_env_size % 512), "env size must be multiple of 512.");
+
+  std::string env_readout = ReadFile(FLAGS_input_path);
+  CF_EXPECT(env_readout.length(), "Input env is empty");
+  CF_EXPECT(
+      env_readout.length() <= (FLAGS_env_size - CRC_SIZE - NULL_PAD_LENGTH),
+      "Input env must fit within env_size specified.");
+
+  std::vector<uint8_t> env_buffer(FLAGS_env_size, PAD_VALUE);
+  uint8_t* env_ptr = env_buffer.data() + CRC_SIZE;
+  memcpy(env_ptr, env_readout.c_str(), FileSize(FLAGS_input_path));
+  env_ptr[env_readout.length()] = 0;  // final byte after the env must be NULL
+  uint32_t crc = crc32(0, env_ptr, FLAGS_env_size - CRC_SIZE);
+  memcpy(env_buffer.data(), &crc, sizeof(uint32_t));
+
+  auto output_fd =
+      SharedFD::Creat(FLAGS_output_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+  if (!output_fd->IsOpen()) {
+    return CF_ERR("Couldn't open the output file " + FLAGS_output_path);
+  } else if (FLAGS_env_size !=
+             WriteAll(output_fd, (char*)env_buffer.data(), FLAGS_env_size)) {
+    RemoveFile(FLAGS_output_path);
+    return CF_ERR("Couldn't complete write to " + FLAGS_output_path);
+  }
+
+  return 0;
+}
+}  // namespace cuttlefish
+
+int main(int argc, char** argv) {
+  auto res = cuttlefish::MkenvimageSlimMain(argc, argv);
+  CHECK(res.ok()) << "mkenvimage_slim failed: \n" << res.error();
+  return *res;
+}
diff --git a/host/commands/modem_simulator/Android.bp b/host/commands/modem_simulator/Android.bp
index c0d0371..c9a6bfb 100644
--- a/host/commands/modem_simulator/Android.bp
+++ b/host/commands/modem_simulator/Android.bp
@@ -38,6 +38,7 @@
         "nvram_config.cpp"
     ],
     shared_libs: [
+        "libext2_blkid",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libbase",
diff --git a/host/commands/modem_simulator/call_service.cpp b/host/commands/modem_simulator/call_service.cpp
index 678da4f..674c153 100644
--- a/host/commands/modem_simulator/call_service.cpp
+++ b/host/commands/modem_simulator/call_service.cpp
@@ -194,7 +194,7 @@
       client.SendCommandResponse(kCmeErrorNoNetworkService);
       return;
     }
-    auto local_host_port = GetHostPort();
+    auto local_host_port = GetHostId();
     if (local_host_port == remote_port) {
       client.SendCommandResponse(kCmeErrorOperationNotAllowed);
       return;
@@ -219,11 +219,13 @@
     int index = last_active_call_index_++;
 
     auto call_token = std::make_pair(index, call_status.number);
-    call_status.timeout_serial = thread_looper_->PostWithDelay(
-        std::chrono::minutes(1),
-        makeSafeCallback<CallService>(this, [call_token](CallService* me) {
-          me->TimerWaitingRemoteCallResponse(call_token);
-        }));
+    call_status.timeout_serial = thread_looper_->Post(
+        makeSafeCallback<CallService>(this,
+                                      [call_token](CallService* me) {
+                                        me->TimerWaitingRemoteCallResponse(
+                                            call_token);
+                                      }),
+        std::chrono::minutes(1));
 
     active_calls_[index] = call_status;
   } else {
@@ -237,8 +239,9 @@
       in_emergency_mode_ = true;
       SendUnsolicitedCommand("+WSOS: 1");
     }
-    thread_looper_->PostWithDelay(std::chrono::seconds(1),
-        makeSafeCallback(this, &CallService::SimulatePendingCallsAnswered));
+    thread_looper_->Post(
+        makeSafeCallback(this, &CallService::SimulatePendingCallsAnswered),
+        std::chrono::seconds(1));
   }
 
   client.SendCommandResponse("OK");
@@ -249,11 +252,9 @@
                                          CallStatus::CallState state) {
   if (call.is_remote_call && call.remote_client != std::nullopt) {
     std::stringstream ss;
-    ss << "AT+REMOTECALL=" << state << ","
-                           << call.is_voice_mode << ","
-                           << call.is_multi_party << ",\""
-                           << GetHostPort() << "\","
-                           << call.is_international;
+    ss << "AT+REMOTECALL=" << state << "," << call.is_voice_mode << ","
+       << call.is_multi_party << ",\"" << GetHostId() << "\","
+       << call.is_international;
 
     SendCommandToRemote(*(call.remote_client), ss.str());
     if (state == CallStatus::CALL_STATE_HANGUP) {
@@ -702,7 +703,7 @@
       call_status.is_voice_mode = mode;
       call_status.is_multi_party = mpty;
       call_status.is_mobile_terminated = true;
-      call_status.is_international = num_type;
+      call_status.is_international = (num_type == 145);
       call_status.remote_client = client.client_fd;
       call_status.call_state = CallStatus::CALL_STATE_INCOMING;
 
diff --git a/host/commands/modem_simulator/cf_device_config.cpp b/host/commands/modem_simulator/cf_device_config.cpp
index 559e1f8..1201db5 100644
--- a/host/commands/modem_simulator/cf_device_config.cpp
+++ b/host/commands/modem_simulator/cf_device_config.cpp
@@ -22,14 +22,13 @@
 namespace cuttlefish {
 namespace modem {
 
-int DeviceConfig::host_port() {
+int DeviceConfig::host_id() {
   if (!cuttlefish::CuttlefishConfig::Get()) {
-      return 6500;
+    return 1000;
   }
   auto config = cuttlefish::CuttlefishConfig::Get();
   auto instance = config->ForDefaultInstance();
-  auto host_port = instance.host_port();
-  return host_port;
+  return instance.modem_simulator_host_id();
 }
 
 std::string DeviceConfig::PerInstancePath(const char* file_name) {
@@ -72,5 +71,13 @@
   return ril_config.dns();
 }
 
+std::ifstream DeviceConfig::open_ifstream_crossplat(const char* filename) {
+    return std::ifstream(filename);
+}
+
+std::ofstream DeviceConfig::open_ofstream_crossplat(const char* filename, std::ios_base::openmode mode) {
+    return std::ofstream(filename, mode);
+}
+
 }  // namespace modem
 }  // namespace cuttlefish
diff --git a/host/commands/modem_simulator/channel_monitor.cpp b/host/commands/modem_simulator/channel_monitor.cpp
index e598f2d..941a8b4 100644
--- a/host/commands/modem_simulator/channel_monitor.cpp
+++ b/host/commands/modem_simulator/channel_monitor.cpp
@@ -45,7 +45,7 @@
   if (response.back() != '\r') {
     response += '\r';
   }
-  LOG(VERBOSE) << " AT< " << response;
+  LOG(DEBUG) << " AT< " << response;
 
   std::lock_guard<std::mutex> autolock(const_cast<Client*>(this)->write_mutex);
   client_fd->Write(response.data(), response.size());
@@ -153,7 +153,7 @@
     if (r_pos != std::string::npos) {
       auto command = commands.substr(pos, r_pos - pos);
       if (command.size() > 0) {  // "\r\r" ?
-        LOG(VERBOSE) << "AT> " << command;
+        LOG(DEBUG) << "AT> " << command;
         modem_->DispatchCommand(client, command);
       }
       pos = r_pos + 1;  // Skip '\r'
diff --git a/host/commands/modem_simulator/data_service.cpp b/host/commands/modem_simulator/data_service.cpp
index b48a777..1e11e69 100644
--- a/host/commands/modem_simulator/data_service.cpp
+++ b/host/commands/modem_simulator/data_service.cpp
@@ -335,10 +335,10 @@
 
   // call again after 1 sec delay
   count--;
-  thread_looper_->PostWithDelay(
-      std::chrono::seconds(1),
+  thread_looper_->Post(
       makeSafeCallback(this, &DataService::updatePhysicalChannelconfigs,
-                       modem_tech, freq, cellBandwidthDownlink, count));
+                       modem_tech, freq, cellBandwidthDownlink, count),
+      std::chrono::seconds(1));
 }
 
 }  // namespace cuttlefish
diff --git a/host/commands/modem_simulator/device_config.h b/host/commands/modem_simulator/device_config.h
index 05bca77..c357356 100644
--- a/host/commands/modem_simulator/device_config.h
+++ b/host/commands/modem_simulator/device_config.h
@@ -17,7 +17,8 @@
 #pragma once
 
 #include <string>
-
+#include <fstream>
+ 
 // this file provide a few device (cvd or emulator) specific hooks for
 // modem-simulator
 
@@ -26,12 +27,14 @@
 
 class DeviceConfig {
  public:
-  static int host_port();
+  static int host_id();
   static std::string PerInstancePath(const char* file_name);
   static std::string DefaultHostArtifactsPath(const std::string& file);
   static std::string ril_address_and_prefix();
   static std::string ril_gateway();
   static std::string ril_dns();
+  static std::ifstream open_ifstream_crossplat(const char* filename);
+  static std::ofstream open_ofstream_crossplat(const char* filename, std::ios_base::openmode mode = std::ios_base::out);
 };
 
 }  // namespace modem
diff --git a/host/commands/modem_simulator/main.cpp b/host/commands/modem_simulator/main.cpp
index 794ad77..41ab6ce 100644
--- a/host/commands/modem_simulator/main.cpp
+++ b/host/commands/modem_simulator/main.cpp
@@ -63,7 +63,7 @@
   auto config = cuttlefish::CuttlefishConfig::Get();
   auto instance = config->ForDefaultInstance();
 
-  auto modem_log_path = instance.PerInstancePath("modem_simulator.log");
+  auto modem_log_path = instance.PerInstanceLogPath("modem_simulator.log");
 
   {
     auto log_path = instance.launcher_log_path();
@@ -114,7 +114,7 @@
   // remote call, remote sms from other cuttlefish instance
   std::string monitor_socket_name = "modem_simulator";
   std::stringstream ss;
-  ss << instance.host_port();
+  ss << instance.modem_simulator_host_id();
   monitor_socket_name.append(ss.str());
 
   auto monitor_socket = cuttlefish::SharedFD::SocketLocalServer(
diff --git a/host/commands/modem_simulator/misc_service.cpp b/host/commands/modem_simulator/misc_service.cpp
index 1a2b767..005d9f9 100644
--- a/host/commands/modem_simulator/misc_service.cpp
+++ b/host/commands/modem_simulator/misc_service.cpp
@@ -15,10 +15,11 @@
 
 #include "host/commands/modem_simulator/misc_service.h"
 
-#include <ctime>
 #include <fstream>
 #include <iomanip>
 
+#include "host/commands/modem_simulator/device_config.h"
+
 namespace cuttlefish {
 
 MiscService::MiscService(int32_t service_id, ChannelMonitor* channel_monitor,
@@ -31,7 +32,7 @@
 void MiscService::ParseTimeZone() {
 #if defined(__linux__)
   constexpr char TIMEZONE_FILENAME[] = "/etc/timezone";
-  std::ifstream ifs(TIMEZONE_FILENAME);
+  std::ifstream ifs = modem::DeviceConfig::open_ifstream_crossplat(TIMEZONE_FILENAME);
   if (ifs.is_open()) {
     std::string line;
     if (std::getline(ifs, line)) {
@@ -95,6 +96,10 @@
                      [this](const Client& client, std::string& cmd) {
                        this->HandleGetIMEI(client, cmd);
                      }),
+      CommandHandler("+REMOTETIMEUPDATE",
+                     [this](const Client& client, std::string& cmd) {
+                       this->HandleTimeUpdate(client, cmd);
+                     }),
   };
   return (command_handlers);
 }
@@ -135,31 +140,43 @@
   client.SendCommandResponse(responses);
 }
 
+void MiscService::HandleTimeUpdate(const Client& client, std::string& command) {
+    (void)client;
+    (void)command;
+    TimeUpdate();
+}
+
+long MiscService::TimeZoneOffset(time_t* utctime)
+{
+    struct tm local = *std::localtime(utctime);
+    time_t local_time = std::mktime(&local);
+    struct tm gmt = *std::gmtime(utctime);
+    // mktime() converts struct tm according to local timezone.
+    time_t gmt_time = std::mktime(&gmt);
+    return (long)difftime(local_time, gmt_time);
+}
+
 void MiscService::TimeUpdate() {
   auto now = std::time(0);
 
   auto local_time = *std::localtime(&now);
   auto gm_time = *std::gmtime(&now);
 
-  auto t_local_time = std::mktime(&local_time);
-  auto t_gm_time = std::mktime(&gm_time);
-
   // Timezone offset is in number of quarter-hours
-  auto tzdiff = (int)std::difftime(t_local_time, t_gm_time) / (15 * 60);
+  auto tzdiff = TimeZoneOffset(&now) / (15 * 60);
 
   std::stringstream ss;
-  ss << "%CTZV: \"" << std::setfill('0') << std::setw(2)
-     << local_time.tm_year % 100 << "/" << std::setfill('0') << std::setw(2)
-     << local_time.tm_mon + 1 << "/" << std::setfill('0') << std::setw(2)
-     << local_time.tm_mday << "," << std::setfill('0') << std::setw(2)
-     << local_time.tm_hour << ":" << std::setfill('0') << std::setw(2)
-     << local_time.tm_min << ":" << std::setfill('0') << std::setw(2)
-     << local_time.tm_sec << (tzdiff >= 0 ? '+' : '-')
+  ss << "%CTZV: " << std::setfill('0') << std::setw(2)
+     << gm_time.tm_year % 100 << "/" << std::setfill('0') << std::setw(2)
+     << gm_time.tm_mon + 1 << "/" << std::setfill('0') << std::setw(2)
+     << gm_time.tm_mday << ":" << std::setfill('0') << std::setw(2)
+     << gm_time.tm_hour << ":" << std::setfill('0') << std::setw(2)
+     << gm_time.tm_min << ":" << std::setfill('0') << std::setw(2)
+     << gm_time.tm_sec << (tzdiff >= 0 ? '+' : '-')
      << (tzdiff >= 0 ? tzdiff : -tzdiff) << ":" << local_time.tm_isdst;
   if (!timezone_.empty()) {
     ss << ":" << timezone_;
   }
-  ss << "\"";
 
   SendUnsolicitedCommand(ss.str());
 }
diff --git a/host/commands/modem_simulator/misc_service.h b/host/commands/modem_simulator/misc_service.h
index e795a06..bb4c96c 100644
--- a/host/commands/modem_simulator/misc_service.h
+++ b/host/commands/modem_simulator/misc_service.h
@@ -17,6 +17,8 @@
 
 #include "host/commands/modem_simulator/modem_service.h"
 
+#include <ctime>
+
 namespace cuttlefish {
 
 class MiscService : public ModemService, public std::enable_shared_from_this<MiscService>  {
@@ -29,6 +31,7 @@
   MiscService &operator=(const MiscService &) = delete;
 
   void HandleGetIMEI(const Client& client, std::string& command);
+  void HandleTimeUpdate(const Client& client, std::string& command);
 
   void TimeUpdate();
 
@@ -36,6 +39,7 @@
 
  private:
   void ParseTimeZone();
+  long TimeZoneOffset(time_t* utctime); // in seconds.
   void FixTimeZone(std::string& line);
   std::string timezone_;
   std::vector<CommandHandler> InitializeCommandHandlers();
diff --git a/host/commands/modem_simulator/modem_service.cpp b/host/commands/modem_simulator/modem_service.cpp
index 062da6f..7996e31 100644
--- a/host/commands/modem_simulator/modem_service.cpp
+++ b/host/commands/modem_simulator/modem_service.cpp
@@ -137,11 +137,8 @@
   }
 }
 
-std::string ModemService::GetHostPort() {
-  auto host_port = cuttlefish::modem::DeviceConfig::host_port();
-  std::stringstream ss;
-  ss << host_port;
-  return ss.str();
+std::string ModemService::GetHostId() {
+  return std::to_string(cuttlefish::modem::DeviceConfig::host_id());
 }
 
 }  // namespace cuttlefish
diff --git a/host/commands/modem_simulator/modem_service.h b/host/commands/modem_simulator/modem_service.h
index 0b75d7d..744f618 100644
--- a/host/commands/modem_simulator/modem_service.h
+++ b/host/commands/modem_simulator/modem_service.h
@@ -104,7 +104,7 @@
   void SendCommandToRemote(cuttlefish::SharedFD remote_client,
                            std::string response);
   void CloseRemoteConnection(cuttlefish::SharedFD remote_client);
-  static std::string GetHostPort();
+  static std::string GetHostId();
 
   int32_t service_id_;
   const std::vector<CommandHandler> command_handlers_;
diff --git a/host/commands/modem_simulator/modem_simulator.cpp b/host/commands/modem_simulator/modem_simulator.cpp
index 61a23bd..71c9ada 100644
--- a/host/commands/modem_simulator/modem_simulator.cpp
+++ b/host/commands/modem_simulator/modem_simulator.cpp
@@ -146,7 +146,7 @@
 
 bool ModemSimulator::IsWaitingSmsPdu() {
   if (sms_service_) {
-    return (sms_service_->IsWaitingSmsPdu() |
+    return (sms_service_->IsWaitingSmsPdu() ||
             sms_service_->IsWaitingSmsToSim());
   }
   return false;
diff --git a/host/commands/modem_simulator/network_service.cpp b/host/commands/modem_simulator/network_service.cpp
index 56f14e5..4d102fa 100644
--- a/host/commands/modem_simulator/network_service.cpp
+++ b/host/commands/modem_simulator/network_service.cpp
@@ -31,20 +31,12 @@
 // string type; four byte GERAN/UTRAN cell ID in hexadecimal format
 static const std::string kCellId = "0000B804";
 
-// Check SignalStrength.java file for more details on how these map to
-// signal strength bars
-const std::pair<int, int> kGSMSignalStrength = std::make_pair(4, 30);
-const std::pair<int, int> kCDMASignalStrength = std::make_pair(4, 120);
-const std::pair<int, int> kEVDOSignalStrength = std::make_pair(4, 120);
-const std::pair<int, int> kLTESignalStrength = std::make_pair(4, 30);
-const std::pair<int, int> kWCDMASignalStrength = std::make_pair(4, 30);
-const std::pair<int, int> kNRSignalStrength = std::make_pair(45, 135);
-
 NetworkService::NetworkService(int32_t service_id,
                                ChannelMonitor* channel_monitor,
                                ThreadLooper* thread_looper)
     : ModemService(service_id, this->InitializeCommandHandlers(),
-                   channel_monitor, thread_looper) {
+                   channel_monitor, thread_looper),
+      keep_signal_strength_changing_loop_(*this) {
   InitializeServiceState();
 }
 
@@ -142,6 +134,8 @@
 
   first_signal_strength_request_ = true;
   android_last_signal_time_ = 0;
+
+  keep_signal_strength_changing_loop_.Start();
 }
 
 void NetworkService::InitializeNetworkOperator() {
@@ -255,9 +249,10 @@
     // Note: not saved to nvram config due to sim status may change after reboot
     current_network_mode_ = M_MODEM_TECH_WCDMA;
   }
-  thread_looper_->PostWithDelay(std::chrono::seconds(1),
+  thread_looper_->Post(
       makeSafeCallback(this, &NetworkService::UpdateRegisterState,
-          voice_registration_status_.registration_state));
+                       voice_registration_status_.registration_state),
+      std::chrono::seconds(1));
 }
 
 /**
@@ -318,7 +313,6 @@
       client.SendCommandResponse(kCmeErrorOperationNotSupported);
       return;
   }
-  signal_strength_.Reset();
 
   client.SendCommandResponse("OK");
 }
@@ -335,39 +329,87 @@
   return wakeup_from_sleep;
 }
 
-void NetworkService::SetSignalStrengthValue(int& value,
-                                            const std::pair<int, int>& range,
-                                            double percentd) {
-  value = range.first + percentd * (range.second - range.first);
-  AdjustSignalStrengthValue(value, range);
-}
-
-void NetworkService::AdjustSignalStrengthValue(int& value,
-                                               const std::pair<int, int>& range) {
-  if (value < range.first) {
-    value = range.first;
-  } else if (value > range.second) {
-    value = range.second;
-  }
-}
 /**
+ * IMPORTANT NOTE: Current implementation of AT+CSQ differs from standards
+ * described in TS 27.007 8.5 which only only supports RSSI and BER.
+ *
+ * TODO(b/206814247): Rename AT+CSQ command.
+ *
  * AT+CSQ
- *   Execution command returns received signal strength indication <rssi>
- *   and channel bit error rate <ber> from the MT.
+ *   Execution command returns received signal strength indication. This is a
+ *   Cuttlefish specific command.
  *
- * command            Possible response(s)
- * AT+CSQ               +CSQ: <rssi>,<ber>
- *                      +CME ERROR: <err>
+ * Command            Possible response(s)
+ * AT+CSQ             +CSQ: <gsm_rssi>,<gsm_ber>,<cdma_dbm>,
+ *                      <cdma_ecio>,<evdo_dbm>,<evdo_ecio>,<evdo_snr>,
+ *                      <lte_rssi>,<lte_rsrp>,<lte_rsrq>,<lte_rssnr>,
+ *                      <lte_cqi>,<lte_ta>,<tdscdma_rscp>,<wcdma_rssi>,
+ *                      <wcdma_ber>,<nr_ss_rsrp>,<nr_ss_rsrq>,<nr_ss_sinr>,
+ *                      <nr_csi_rsrp>,<nr_csi_rsrq>,<nr_csi_sinr>
+ *                    +CME ERROR: <err>
  *
- * <rssi>: integer type
- *       0 ‑113 dBm or less
- *       1 ‑111 dBm
- *       2...30  ‑109... ‑53 dBm
- *       31  ‑51 dBm or greater
- *       99  not known or not detectable
- * <ber>: integer type; channel bit error rate (in percent)
- *      0...7 as RXQUAL values in the table in 3GPP TS 45.008 [20] subclause 8.2.4
- *      99  not known or not detectable
+ * <gsm_rssi>: Valid values are (0-31, 99) as defined in TS 27.007 8.5.
+ * <gsm_ber>: Bit error rate (0-7, 99) as defined in TS 27.007 8.5.
+ * <cdma_dbm>: Valid values are positive integers.
+ *   This value is the actual RSSI value multiplied by -1.
+ *   Example: If the actual RSSI is -75, then this response value will be 75.
+ * <cdma_ecio>: Valid values are positive integers.
+ *   This value is the actual Ec/Io multiplied by -10.
+ *   Example: If the actual Ec/Io is -12.5 dB, then this response value will
+ *   be 125.
+ * <evdo_dbm>: Refer cdma_dbm.
+ * <evdo_ecio>: Refer cdma_ecio.
+ * <evdo_snr>: Valid values are 0-8.
+ *   8 is the highest signal to noise ratio.
+ * <lte_rssi>: Refer gsm_rssi.
+ * <lte_rsrp>:
+ *   The current Reference Signal Receive Power in dBm multiplied by -1.
+ *   Range: 44 to 140 dBm.
+ *   INT_MAX: 0x7FFFFFFF denotes invalid value.
+ *   Reference: 3GPP TS 36.133 9.1.4.
+ * <lte_rsrq>:
+ *   The current Reference Signal Receive Quality in dB multiplied by -1.
+ *   Range: 20 to 3 dB.
+ *   INT_MAX: 0x7FFFFFFF denotes invalid value.
+ *   Reference: 3GPP TS 36.133 9.1.7.
+ * <lte_rssnr>:
+ *   The current reference signal signal-to-noise ratio in 0.1 dB units.
+ *   Range: -200 to +300 (-200 = -20.0 dB, +300 = 30dB).
+ *   INT_MAX : 0x7FFFFFFF denotes invalid value.
+ *   Reference: 3GPP TS 36.101 8.1.1.
+ * <lte_cqi>: The current Channel Quality Indicator.
+ *   Range: 0 to 15.
+ *   INT_MAX : 0x7FFFFFFF denotes invalid value.
+ *   Reference: 3GPP TS 36.101 9.2, 9.3, A.4.
+ * <lte_ta>:
+ *   Timing advance in micro seconds for a one way trip from cell to device.
+ *   Approximate distance can be calculated using 300m/us * timingAdvance.
+ *   Range: 0 to 0x7FFFFFFE.
+ *   INT_MAX : 0x7FFFFFFF denotes invalid value.
+ *   Reference: 3GPP 36.321 section 6.1.3.5.
+ * <tdscdma_rscp>: P-CCPCH RSCP as defined in TS 25.225 5.1.1.
+ *   Valid values are (0-96, 255) as defined in TS 27.007 8.69.
+ *   INT_MAX denotes that the value is invalid/unreported.
+ * <wcdma_rssi>: Refer gsm_rssi.
+ * <wcdma_ber>: Refer gsm_ber.
+ * <nr_ss_rsrp>: SS reference signal received power, multiplied by -1.
+ *   Reference: 3GPP TS 38.215.
+ *   Range [44, 140], INT_MAX means invalid/unreported.
+ * <nr_ss_rsrq>: SS reference signal received quality, multiplied by -1.
+ *   Reference: 3GPP TS 38.215.
+ *   Range [3, 20], INT_MAX means invalid/unreported.
+ * <nr_ss_sinr>: SS signal-to-noise and interference ratio.
+ *   Reference: 3GPP TS 38.215 section 5.1.*, 3GPP TS 38.133 section 10.1.16.1.
+ *   Range [-23, 40], INT_MAX means invalid/unreported.
+ * <nr_csi_rsrp>: CSI reference signal received power, multiplied by -1.
+ *   Reference: 3GPP TS 38.215.
+ *   Range [44, 140], INT_MAX means invalid/unreported.
+ * <nr_csi_rsrq>: CSI reference signal received quality, multiplied by -1.
+ *   Reference: 3GPP TS 38.215.
+ *   Range [3, 20], INT_MAX means invalid/unreported.
+ * <nr_csi_sinr>: CSI signal-to-noise and interference ratio.
+ *   Reference: 3GPP TS 138.215 section 5.1.*, 3GPP TS 38.133 section 10.1.16.1.
+ *   Range [-23, 40], INT_MAX means invalid/unreported.
  *
  * see RIL_REQUEST_SIGNAL_STRENGTH in RIL
  */
@@ -384,7 +426,7 @@
 
   android_last_signal_time_ = time(0);
 
-  auto response = GetSignalStrength();
+  auto response = BuildCSQCommandResponse(GetCurrentSignalStrength());
 
   responses.push_back(response);
   responses.push_back("OK");
@@ -392,11 +434,9 @@
 }
 
 bool NetworkService::IsHasNetwork() {
-  if (radio_state_ == RADIO_STATE_OFF ||
-      oper_selection_mode_ == OperatorSelectionMode::OPER_SELECTION_DEREGISTRATION) {
-    return false;
-  }
-  return true;
+  return radio_state_ != RADIO_STATE_OFF &&
+         oper_selection_mode_ !=
+             OperatorSelectionMode::OPER_SELECTION_DEREGISTRATION;
 }
 
 /**
@@ -676,8 +716,10 @@
 
   NvramConfig::SaveToFile();
 
-  thread_looper_->PostWithDelay(std::chrono::seconds(1),
-      makeSafeCallback(this, &NetworkService::UpdateRegisterState, registration_state));
+  thread_looper_->Post(
+      makeSafeCallback(this, &NetworkService::UpdateRegisterState,
+                       registration_state),
+      std::chrono::seconds(1));
 }
 
 NetworkService::NetworkRegistrationStatus::AccessTechnoloy
@@ -987,13 +1029,13 @@
 
   if (current != current_network_mode_) {
     UpdateRegisterState(NET_REGISTRATION_UNREGISTERED);
-    signal_strength_.Reset();
 
     ss << "+CTEC: "<< current_network_mode_;
 
-    thread_looper_->PostWithDelay(std::chrono::milliseconds(200),
+    thread_looper_->Post(
         makeSafeCallback(this, &NetworkService::UpdateRegisterState,
-            NET_REGISTRATION_HOME));
+                         NET_REGISTRATION_HOME),
+        std::chrono::milliseconds(200));
   } else {
     ss << "+CTEC: DONE";
   }
@@ -1071,60 +1113,75 @@
   SendUnsolicitedCommand(ss.str());
 }
 
-std::string NetworkService::GetSignalStrength() {
+int NetworkService::GetValueInRange(const std::pair<int, int>& range,
+                                    int percent) {
+  int range_size = range.second - range.first + 1;
+  return range.first + (int)((percent / 101.0) * range_size);
+}
+
+std::string NetworkService::BuildCSQCommandResponse(
+    const SignalStrength& signal_strength) {
+  std::stringstream ss;
+  // clang-format off
+  ss << "+CSQ: "
+     << signal_strength.gsm_rssi << ","
+     << signal_strength.gsm_ber << ","
+     << signal_strength.cdma_dbm << ","
+     << signal_strength.cdma_ecio << ","
+     << signal_strength.evdo_dbm << ","
+     << signal_strength.evdo_ecio << ","
+     << signal_strength.evdo_snr << ","
+     << signal_strength.lte_rssi << ","
+     << signal_strength.lte_rsrp << ","
+     << signal_strength.lte_rsrq << ","
+     << signal_strength.lte_rssnr << ","
+     << signal_strength.lte_cqi << ","
+     << signal_strength.lte_ta << ","
+     << signal_strength.tdscdma_rscp << ","
+     << signal_strength.wcdma_rssi << ","
+     << signal_strength.wcdma_ber << ","
+     << signal_strength.nr_ss_rsrp << ","
+     << signal_strength.nr_ss_rsrq << ","
+     << signal_strength.nr_ss_sinr << ","
+     << signal_strength.nr_csi_rsrp << ","
+     << signal_strength.nr_csi_rsrq << ","
+     << signal_strength.nr_csi_sinr;
+  // clang-format on
+  return ss.str();
+}
+
+NetworkService::SignalStrength NetworkService::GetCurrentSignalStrength() {
+  NetworkService::SignalStrength result;
+  if (!IsHasNetwork()) {
+    return result;
+  }
+  int percent = signal_strength_percent_;
   switch (current_network_mode_) {
     case M_MODEM_TECH_GSM:
-      signal_strength_.gsm_rssi += (rand() % 3 - 1);
-      AdjustSignalStrengthValue(signal_strength_.gsm_rssi, kGSMSignalStrength);
+      result.gsm_rssi = GetValueInRange(kRssiRange, percent);
       break;
     case M_MODEM_TECH_CDMA:
-      signal_strength_.cdma_dbm += (rand() % 3 - 1);
-      AdjustSignalStrengthValue(signal_strength_.cdma_dbm, kCDMASignalStrength);
+      result.cdma_dbm = GetValueInRange(kDbmRange, percent) * -1;
       break;
     case M_MODEM_TECH_EVDO:
-      signal_strength_.evdo_dbm += (rand() % 3 - 1);
-      AdjustSignalStrengthValue(signal_strength_.evdo_dbm, kEVDOSignalStrength);
+      result.evdo_dbm = GetValueInRange(kDbmRange, percent) * -1;
       break;
     case M_MODEM_TECH_LTE:
-      signal_strength_.lte_rssi += (rand() % 3 - 1);
-      AdjustSignalStrengthValue(signal_strength_.lte_rssi, kLTESignalStrength);
+      result.lte_rsrp = GetValueInRange(kRsrpRange, percent) * -1;
       break;
     case M_MODEM_TECH_WCDMA:
-      signal_strength_.wcdma_rssi += (rand() % 3 - 1);
-      AdjustSignalStrengthValue(signal_strength_.wcdma_rssi, kWCDMASignalStrength);
+      result.wcdma_rssi = GetValueInRange(kRssiRange, percent);
       break;
     case M_MODEM_TECH_NR:
-      signal_strength_.nr_ss_rsrp += (rand() % 3 - 1);
-      AdjustSignalStrengthValue(signal_strength_.nr_ss_rsrp, kNRSignalStrength);
+      // special for NR: it uses LTE as primary, so LTE signal strength is
+      // needed as well
+      result.lte_rsrp = GetValueInRange(kRsrpRange, percent) * -1;
+      result.nr_ss_rsrp = GetValueInRange(kRsrpRange, percent) * -1;
       break;
     default:
       break;
   }
-
-  std::stringstream ss;
-  ss << "+CSQ: " << signal_strength_.gsm_rssi << ","
-                 << signal_strength_.gsm_ber << ","
-                 << signal_strength_.cdma_dbm << ","
-                 << signal_strength_.cdma_ecio << ","
-                 << signal_strength_.evdo_dbm << ","
-                 << signal_strength_.evdo_ecio << ","
-                 << signal_strength_.evdo_snr << ","
-                 << signal_strength_.lte_rssi << ","
-                 << signal_strength_.lte_rsrp << ","
-                 << signal_strength_.lte_rsrq << ","
-                 << signal_strength_.lte_rssnr << ","
-                 << signal_strength_.lte_cqi << ","
-                 << signal_strength_.lte_ta << ","
-                 << signal_strength_.tdscdma_rscp << ","
-                 << signal_strength_.wcdma_rssi << ","
-                 << signal_strength_.wcdma_ber << ","
-                 << signal_strength_.nr_ss_rsrp << ","
-                 << signal_strength_.nr_ss_rsrq << ","
-                 << signal_strength_.nr_ss_sinr << ","
-                 << signal_strength_.nr_csi_rsrp << ","
-                 << signal_strength_.nr_csi_rsrq << ","
-                 << signal_strength_.nr_csi_sinr;;
-  return ss.str();
+  return result;
 }
 
 /* AT+REMOTEREG: state*/
@@ -1136,12 +1193,11 @@
   int stated = std::stoi(states, nullptr, 10);
 
   UpdateRegisterState(NET_REGISTRATION_UNREGISTERED);
-  signal_strength_.Reset();
 
-  thread_looper_->PostWithDelay(
-      std::chrono::seconds(1),
+  thread_looper_->Post(
       makeSafeCallback(this, &NetworkService::UpdateRegisterState,
-                       (cuttlefish::NetworkService::RegistrationState)stated));
+                       (cuttlefish::NetworkService::RegistrationState)stated),
+      std::chrono::seconds(1));
 }
 
 /* AT+REMOTECTEC: ctec */
@@ -1162,77 +1218,13 @@
     current_network_mode_ = current_network_mode_new;
     auto saved_state = voice_registration_status_.registration_state;
     UpdateRegisterState(NET_REGISTRATION_UNREGISTERED);
-    signal_strength_.Reset();
 
     ss << "+CTEC: " << current_network_mode_;
 
-    thread_looper_->PostWithDelay(
-        std::chrono::seconds(1),
+    thread_looper_->Post(
         makeSafeCallback(this, &NetworkService::UpdateRegisterState,
-                         saved_state));
-  }
-}
-
-void NetworkService::applySignalPercentage(double percentd) {
-  switch (current_network_mode_) {
-    case M_MODEM_TECH_GSM:
-      signal_strength_.gsm_rssi = 99;
-      signal_strength_.gsm_ber = 0;
-      SetSignalStrengthValue(signal_strength_.gsm_rssi, kGSMSignalStrength,
-                             percentd);
-      break;
-    case M_MODEM_TECH_CDMA:
-      signal_strength_.cdma_dbm = 125;
-      signal_strength_.cdma_ecio = 165;
-      SetSignalStrengthValue(signal_strength_.cdma_dbm, kCDMASignalStrength,
-                             percentd);
-      break;
-    case M_MODEM_TECH_EVDO:
-      signal_strength_.evdo_dbm = 125;
-      signal_strength_.evdo_ecio = 165;
-      signal_strength_.evdo_snr = -1;
-      SetSignalStrengthValue(signal_strength_.evdo_dbm, kEVDOSignalStrength,
-                             percentd);
-      break;
-    case M_MODEM_TECH_LTE:
-      signal_strength_.lte_rssi = 99;
-      signal_strength_.lte_rsrp = -1;
-      signal_strength_.lte_rsrq = -5;
-      signal_strength_.lte_rssnr = -205;
-      signal_strength_.lte_cqi = -1;
-      signal_strength_.lte_ta = -1;
-      SetSignalStrengthValue(signal_strength_.lte_rssi, kLTESignalStrength,
-                             percentd);
-      break;
-    case M_MODEM_TECH_WCDMA:
-      signal_strength_.tdscdma_rscp = 99;
-      signal_strength_.wcdma_rssi = 99;
-      signal_strength_.wcdma_ber = 0;
-      SetSignalStrengthValue(signal_strength_.wcdma_rssi, kWCDMASignalStrength,
-                             percentd);
-      break;
-    case M_MODEM_TECH_NR:
-      // special for NR: it uses LTE as primary, so LTE signal strength is
-      // needed as well
-      signal_strength_.lte_rssi = 99;
-      signal_strength_.lte_rsrp = -1;
-      signal_strength_.lte_rsrq = -5;
-      signal_strength_.lte_rssnr = -205;
-      signal_strength_.lte_cqi = -1;
-      signal_strength_.lte_ta = -1;
-      SetSignalStrengthValue(signal_strength_.lte_rssi, kLTESignalStrength,
-                             percentd);
-      signal_strength_.nr_ss_rsrp = 0;
-      signal_strength_.nr_ss_rsrq = 0;
-      signal_strength_.nr_ss_sinr = 45;
-      signal_strength_.nr_csi_rsrp = 0;
-      signal_strength_.nr_csi_rsrq = 0;
-      signal_strength_.nr_csi_sinr = 30;
-      SetSignalStrengthValue(signal_strength_.nr_ss_rsrp, kNRSignalStrength,
-                             percentd);
-      break;
-    default:
-      break;
+                         saved_state),
+        std::chrono::seconds(1));
   }
 }
 
@@ -1242,12 +1234,12 @@
   (void)client;
   std::stringstream ss;
   std::string percents = command.substr(std::string("AT+REMOTESIGNAL:").size());
-  double percentd = std::stoi(percents, nullptr, 10) / 100.0;
+  int percent = std::stoi(percents, nullptr, 10);
 
-  if (percentd >= 0 && percentd <= 1.0) {
-    percentd_ = percentd;
+  if (percent >= 0 && percent <= 100) {
+    signal_strength_percent_ = percent;
   } else {
-    LOG(DEBUG) << "out of bound signal strength: " << percentd;
+    LOG(DEBUG) << "out of bound signal strength percent: " << percent;
     return;
   }
 
@@ -1255,12 +1247,42 @@
 }
 
 void NetworkService::OnSignalStrengthChanged() {
-  applySignalPercentage(percentd_);
-  auto command = GetSignalStrength();
-  SendUnsolicitedCommand(command);
+  SendUnsolicitedCommand(BuildCSQCommandResponse(GetCurrentSignalStrength()));
 }
 
 NetworkService::RegistrationState NetworkService::GetVoiceRegistrationState() const {
   return voice_registration_status_.registration_state;
 }
+
+NetworkService::KeepSignalStrengthChangingLoop::KeepSignalStrengthChangingLoop(
+    NetworkService& network_service)
+    : network_service_{network_service}, loop_started_ ATOMIC_FLAG_INIT {}
+
+void NetworkService::KeepSignalStrengthChangingLoop::Start() {
+  if (loop_started_.test_and_set()) {
+    LOG(ERROR) << "Signal strength is already changing automatically";
+  } else {
+    UpdateSignalStrengthCallback();
+  }
+}
+
+void NetworkService::KeepSignalStrengthChangingLoop::
+    UpdateSignalStrengthCallback() {
+  if (network_service_.IsHasNetwork()) {
+    network_service_.signal_strength_percent_ -= 5;
+    // With "close to 0" values, the signal strength bar on the Android UI will
+    // be shown empty, this also represents that theres's no connectivity which
+    // is missleading as the connectivity continues, so a lower bound of 10 will
+    // be used so the signal strenght bar is never emptied
+    if (network_service_.signal_strength_percent_ <= 10) {
+      network_service_.signal_strength_percent_ = 100;
+    }
+    network_service_.OnSignalStrengthChanged();
+  }
+  network_service_.thread_looper_->Post(
+      makeSafeCallback(this, &NetworkService::KeepSignalStrengthChangingLoop::
+                                 UpdateSignalStrengthCallback),
+      std::chrono::seconds(10));
+}
+
 }  // namespace cuttlefish
diff --git a/host/commands/modem_simulator/network_service.h b/host/commands/modem_simulator/network_service.h
index b64d4b9..37808fb 100644
--- a/host/commands/modem_simulator/network_service.h
+++ b/host/commands/modem_simulator/network_service.h
@@ -20,6 +20,7 @@
 #include "host/commands/modem_simulator/data_service.h"
 #include "host/commands/modem_simulator/misc_service.h"
 #include "host/commands/modem_simulator/modem_service.h"
+#include "host/commands/modem_simulator/network_service_constants.h"
 #include "host/commands/modem_simulator/sim_service.h"
 
 namespace cuttlefish {
@@ -82,10 +83,6 @@
   bool IsHasNetwork();
   void UpdateRegisterState(RegistrationState state);
   void AdjustSignalStrengthValue(int& value, const std::pair<int, int>& range);
-  void SetSignalStrengthValue(int& value, const std::pair<int, int>& range,
-                              double percentd);
-  std::string GetSignalStrength();
-  void applySignalPercentage(double percentd);
 
   MiscService* misc_service_ = nullptr;
   SimService* sim_service_ = nullptr;
@@ -202,61 +199,41 @@
                            * Reference: 3GPP TS 138.215 section 5.1.*, 3GPP TS 38.133 section 10.1.16.1.
                            * Range [-23, 40], INT_MAX means invalid/unreported. */
 
-    // Default invalid value
-    SignalStrength():
-      gsm_rssi(99),     // [0, 31]
-      gsm_ber(0),       // [7, 99]
-      cdma_dbm(125),    // [0, 120]
-      cdma_ecio(165),   // [0, 160]
-      evdo_dbm(125),    // [0, 120]
-      evdo_ecio(165),   // [0, 160]
-      evdo_snr(-1),     // [0, 8]
-      lte_rssi(99),     // [0, 31]
-      lte_rsrp(-1),     // [43,140]
-      lte_rsrq(-5),     // [-3,34]
-      lte_rssnr(-205),  // [-200, 300]
-      lte_cqi(-1),      // [0, 15]
-      lte_ta(-1),       // [0, 1282]
-      tdscdma_rscp(99), // [0, 96]
-      wcdma_rssi(99),   // [0, 31]
-      wcdma_ber(0),     // [7, 99]
-      nr_ss_rsrp(0),    // [44, 140]
-      nr_ss_rsrq(0),    // [3, 10]
-      nr_ss_sinr(45),   // [-23,40]
-      nr_csi_rsrp(0),   // [44, 140]
-      nr_csi_rsrq(0),   // [3, 20]
-      nr_csi_sinr(30)   // [-23, 23]
-      {}
-
-    // After radio power on, off, or set network mode, reset to invalid value
-    void Reset() {
-      gsm_rssi = INT_MAX;
-      gsm_ber = INT_MAX;
-      cdma_dbm = INT_MAX;
-      cdma_ecio = INT_MAX;
-      evdo_dbm = INT_MAX;
-      evdo_ecio = INT_MAX;
-      evdo_snr = INT_MAX;
-      lte_rssi = INT_MAX;
-      lte_rsrp = INT_MAX;
-      lte_rsrq = INT_MAX;
-      lte_rssnr = INT_MAX;
-      lte_cqi = INT_MAX;
-      lte_ta = INT_MAX;
-      tdscdma_rscp = INT_MAX;
-      wcdma_rssi = INT_MAX;
-      wcdma_ber = INT_MAX;
-      nr_ss_rsrp = INT_MAX;
-      nr_ss_rsrq = INT_MAX;
-      nr_ss_sinr = INT_MAX;
-      nr_csi_rsrp = INT_MAX;
-      nr_csi_rsrq = INT_MAX;
-      nr_csi_sinr = INT_MAX;
-    }
+    SignalStrength()
+        : gsm_rssi(kRssiUnknownValue),
+          gsm_ber(kBerUnknownValue),
+          cdma_dbm(kDbmUnknownValue),
+          cdma_ecio(kEcioUnknownValue),
+          evdo_dbm(kDbmUnknownValue),
+          evdo_ecio(kEcioUnknownValue),
+          evdo_snr(kSnrUnknownValue),
+          lte_rssi(kRssiUnknownValue),
+          lte_rsrp(INT_MAX),
+          lte_rsrq(INT_MAX),
+          lte_rssnr(INT_MAX),
+          lte_cqi(INT_MAX),
+          lte_ta(INT_MAX),
+          tdscdma_rscp(INT_MAX),
+          wcdma_rssi(kRssiUnknownValue),
+          wcdma_ber(kBerUnknownValue),
+          nr_ss_rsrp(INT_MAX),
+          nr_ss_rsrq(INT_MAX),
+          nr_ss_sinr(INT_MAX),
+          nr_csi_rsrp(INT_MAX),
+          nr_csi_rsrq(INT_MAX),
+          nr_csi_sinr(INT_MAX) {}
   };
 
-  double percentd_{0.8};
-  SignalStrength signal_strength_;
+  // There's no such thing as a percentange for signal strength in the real
+  // world, as for example for battery usage, this percent value is used to pick
+  // a value within the corresponding signal strength values range for emulation
+  // purposes only.
+  int signal_strength_percent_{80};
+
+  static int GetValueInRange(const std::pair<int, int>& range, int percent);
+  static std::string BuildCSQCommandResponse(
+      const SignalStrength& signal_strength);
+  SignalStrength GetCurrentSignalStrength();
 
   /* Data / voice Registration State */
   struct NetworkRegistrationStatus {
@@ -315,6 +292,20 @@
 
   bool first_signal_strength_request_;  // For time update
   time_t android_last_signal_time_;
+
+  class KeepSignalStrengthChangingLoop {
+   public:
+    KeepSignalStrengthChangingLoop(NetworkService& network_service);
+    void Start();
+
+   private:
+    void UpdateSignalStrengthCallback();
+
+    NetworkService& network_service_;
+    std::atomic_flag loop_started_;
+  };
+
+  KeepSignalStrengthChangingLoop keep_signal_strength_changing_loop_;
 };
 
 }  // namespace cuttlefish
diff --git a/host/commands/modem_simulator/network_service_constants.h b/host/commands/modem_simulator/network_service_constants.h
new file mode 100644
index 0000000..49d0319
--- /dev/null
+++ b/host/commands/modem_simulator/network_service_constants.h
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+namespace cuttlefish {
+
+// Constants representing a not known or not detectable value for different
+// signal strength parameters.
+constexpr int kRssiUnknownValue = 99;
+constexpr int kBerUnknownValue = 99;
+constexpr int kDbmUnknownValue = -1;
+constexpr int kEcioUnknownValue = -1;
+constexpr int kSnrUnknownValue = -1;
+
+// Constants representing the range of values of different signal strength
+// parameters.
+constexpr auto kRssiRange = std::make_pair(4, 30);
+constexpr auto kDbmRange = std::make_pair(-120, -4);
+constexpr auto kRsrpRange = std::make_pair(-140, -44);
+
+}  // namespace cuttlefish
diff --git a/host/commands/modem_simulator/nvram_config.cpp b/host/commands/modem_simulator/nvram_config.cpp
index 7efb4c9..f61f96f 100644
--- a/host/commands/modem_simulator/nvram_config.cpp
+++ b/host/commands/modem_simulator/nvram_config.cpp
@@ -115,7 +115,7 @@
   }
 
   Json::CharReaderBuilder builder;
-  std::ifstream ifs(real_file_path);
+  std::ifstream ifs = modem::DeviceConfig::open_ifstream_crossplat(real_file_path.c_str());
   std::string errorMessage;
   if (!Json::parseFromStream(builder, ifs, dictionary_.get(), &errorMessage)) {
     LOG(ERROR) << "Could not read config file " << file << ": "
@@ -126,7 +126,7 @@
 }
 
 bool NvramConfig::SaveToFile(const std::string& file) const {
-  std::ofstream ofs(file);
+  std::ofstream ofs = modem::DeviceConfig::open_ofstream_crossplat(file.c_str());
   if (!ofs.is_open()) {
     LOG(ERROR) << "Unable to write to file " << file;
     return false;
diff --git a/host/commands/modem_simulator/sms_service.cpp b/host/commands/modem_simulator/sms_service.cpp
index 0da21a6..0caee16 100644
--- a/host/commands/modem_simulator/sms_service.cpp
+++ b/host/commands/modem_simulator/sms_service.cpp
@@ -256,8 +256,8 @@
     return;
   }
 
-  auto local_host_port = GetHostPort();
-  auto pdu = sms_pdu.CreateRemotePDU(local_host_port);
+  auto local_host_id = GetHostId();
+  auto pdu = sms_pdu.CreateRemotePDU(local_host_id);
 
   std::string command = "AT+REMOTESMS=" + pdu + "\r";
   std::string token = "REM0";
@@ -292,25 +292,22 @@
     return;
   } else if (port >= kRemotePortRange.first &&
              port <= kRemotePortRange.second) {
-    std::stringstream ss;
-    ss << port;
-    auto remote_host_port = ss.str();
-    if (GetHostPort() == remote_host_port) {  // Send SMS to local host port
-      thread_looper_->PostWithDelay(
-          std::chrono::seconds(1),
-          makeSafeCallback<SmsService>(this, [&sms_pdu](SmsService* me) {
-            me->HandleReceiveSMS(sms_pdu);
-          }));
+    auto remote_host_port = std::to_string(port);
+    if (GetHostId() == remote_host_port) {  // Send SMS to local host port
+      thread_looper_->Post(
+          makeSafeCallback<SmsService>(
+              this,
+              [&sms_pdu](SmsService* me) { me->HandleReceiveSMS(sms_pdu); }),
+          std::chrono::seconds(1));
     } else {  // Send SMS to remote host port
       SendSmsToRemote(remote_host_port, sms_pdu);
     }
   } else if (sim_service_ && phone_number == sim_service_->GetPhoneNumber()) {
     /* Local phone number */
-    thread_looper_->PostWithDelay(
-        std::chrono::seconds(1),
-        makeSafeCallback<SmsService>(this, [sms_pdu](SmsService* me) {
-          me->HandleReceiveSMS(sms_pdu);
-        }));
+    thread_looper_->Post(
+        makeSafeCallback<SmsService>(
+            this, [sms_pdu](SmsService* me) { me->HandleReceiveSMS(sms_pdu); }),
+        std::chrono::seconds(1));
   } /* else pretend send SMS success */
 
   std::stringstream ss;
@@ -321,11 +318,12 @@
 
   if (sms_pdu.IsNeededStatuReport()) {
     int ref = message_reference_;
-    thread_looper_->PostWithDelay(
-        std::chrono::seconds(1),
-        makeSafeCallback<SmsService>(this, [sms_pdu, ref](SmsService* me) {
-          me->HandleSMSStatuReport(sms_pdu, ref);
-        }));
+    thread_looper_->Post(
+        makeSafeCallback<SmsService>(this,
+                                     [sms_pdu, ref](SmsService* me) {
+                                       me->HandleSMSStatuReport(sms_pdu, ref);
+                                     }),
+        std::chrono::seconds(1));
   }
 }
 
diff --git a/host/commands/modem_simulator/thread_looper.cpp b/host/commands/modem_simulator/thread_looper.cpp
index d9df9df..cc6c39e 100644
--- a/host/commands/modem_simulator/thread_looper.cpp
+++ b/host/commands/modem_simulator/thread_looper.cpp
@@ -42,8 +42,8 @@
   return serial;
 }
 
-ThreadLooper::Serial ThreadLooper::PostWithDelay(
-        std::chrono::steady_clock::duration delay, Callback cb) {
+ThreadLooper::Serial ThreadLooper::Post(
+    Callback cb, std::chrono::steady_clock::duration delay) {
   CHECK(cb != nullptr);
 
   auto serial = next_serial_++;
diff --git a/host/commands/modem_simulator/thread_looper.h b/host/commands/modem_simulator/thread_looper.h
index dbfd201..dcc4efb 100644
--- a/host/commands/modem_simulator/thread_looper.h
+++ b/host/commands/modem_simulator/thread_looper.h
@@ -60,7 +60,7 @@
   typedef int32_t Serial;
 
   Serial Post(Callback cb);
-  Serial PostWithDelay(std::chrono::steady_clock::duration delay, Callback cb);
+  Serial Post(Callback cb, std::chrono::steady_clock::duration delay);
 
   void Stop();
 
diff --git a/host/commands/modem_simulator/unittest/service_test.cpp b/host/commands/modem_simulator/unittest/service_test.cpp
index 5801586..4bcbcb0 100644
--- a/host/commands/modem_simulator/unittest/service_test.cpp
+++ b/host/commands/modem_simulator/unittest/service_test.cpp
@@ -23,6 +23,7 @@
 #include "common/libs/fs/shared_select.h"
 #include "common/libs/utils/files.h"
 #include "host/commands/modem_simulator/channel_monitor.h"
+#include "host/commands/modem_simulator/device_config.h"
 #include "host/commands/modem_simulator/modem_simulator.h"
 #include "host/libs/config/cuttlefish_config.h"
 namespace fs = std::filesystem;
@@ -41,31 +42,31 @@
     {
       cuttlefish::CuttlefishConfig tmp_config_obj;
       std::string config_file = tmp_test_dir + "/.cuttlefish_config.json";
-      std::string instance_dir = tmp_test_dir + "/cuttlefish_runtime.1";
-      fs::create_directories(instance_dir);
+      tmp_config_obj.set_root_dir(tmp_test_dir + "/cuttlefish");
       tmp_config_obj.set_ril_dns("8.8.8.8");
       std::vector<int> instance_nums;
       for (int i = 0; i < 1; i++) {
         instance_nums.push_back(cuttlefish::GetInstance() + i);
       }
       for (const auto &num : instance_nums) {
-        auto instance = tmp_config_obj.ForInstance(num);
-        instance.set_instance_dir(instance_dir);
+        tmp_config_obj.ForInstance(num);  // Trigger creation in map
       }
 
       for (auto instance : tmp_config_obj.Instances()) {
+        fs::create_directories(instance.instance_dir());
         if (!tmp_config_obj.SaveToFile(
                 instance.PerInstancePath("cuttlefish_config.json"))) {
           LOG(ERROR) << "Unable to save copy config object";
           return;
         }
+        std::string icfilename =
+            instance.PerInstancePath("/iccprofile_for_sim0.xml");
+        std::ofstream offile = modem::DeviceConfig::open_ofstream_crossplat(icfilename.c_str(), std::ofstream::out);
+        offile << std::string(myiccfile);
+        offile.close();
+        fs::copy_file(instance.PerInstancePath("/cuttlefish_config.json"),
+                      config_file, fs::copy_options::overwrite_existing);
       }
-      fs::copy_file(instance_dir + "/cuttlefish_config.json", config_file,
-                    fs::copy_options::overwrite_existing);
-      std::string icfilename = instance_dir + "/iccprofile_for_sim0.xml";
-      std::ofstream offile(icfilename, std::ofstream::out);
-      offile << std::string(myiccfile);
-      offile.close();
 
       ::setenv("CUTTLEFISH_CONFIG_FILE", config_file.c_str(), 1);
     }
diff --git a/host/commands/powerwash_cvd/Android.bp b/host/commands/powerwash_cvd/Android.bp
index adaacb9..5695f8b 100644
--- a/host/commands/powerwash_cvd/Android.bp
+++ b/host/commands/powerwash_cvd/Android.bp
@@ -23,9 +23,11 @@
         "powerwash_cvd.cc",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
+        "libfruit",
         "libjsoncpp",
     ],
     static_libs: [
diff --git a/host/commands/restart_cvd/Android.bp b/host/commands/restart_cvd/Android.bp
index dc7a044..8b1b971 100644
--- a/host/commands/restart_cvd/Android.bp
+++ b/host/commands/restart_cvd/Android.bp
@@ -23,9 +23,11 @@
         "restart_cvd.cc",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
+        "libfruit",
         "libjsoncpp",
     ],
     static_libs: [
diff --git a/host/commands/restart_cvd/restart_cvd.cc b/host/commands/restart_cvd/restart_cvd.cc
index 68ff3e6..6c165f9 100644
--- a/host/commands/restart_cvd/restart_cvd.cc
+++ b/host/commands/restart_cvd/restart_cvd.cc
@@ -44,7 +44,6 @@
 #include "common/libs/utils/environment.h"
 #include "host/commands/run_cvd/runner_defs.h"
 #include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/vm_manager/vm_manager.h"
 
 DEFINE_int32(instance_num, cuttlefish::GetInstance(),
              "Which instance to restart");
diff --git a/host/commands/run_cvd/Android.bp b/host/commands/run_cvd/Android.bp
index 09b11d5..2f6eb0b 100644
--- a/host/commands/run_cvd/Android.bp
+++ b/host/commands/run_cvd/Android.bp
@@ -22,28 +22,33 @@
     srcs: [
         "boot_state_machine.cc",
         "launch.cc",
-        "launch_adb.cpp",
         "launch_modem.cpp",
         "launch_streamer.cpp",
         "main.cc",
+        "reporting.cpp",
         "process_monitor.cc",
         "server_loop.cpp",
+        "validate.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libcuttlefish_kernel_log_monitor_utils",
         "libbase",
+        "libfruit",
         "libjsoncpp",
         "libnl",
     ],
     static_libs: [
         "libcuttlefish_host_config",
+        "libcuttlefish_host_config_adb",
         "libcuttlefish_vm_manager",
         "libgflags",
     ],
     defaults: [
         "cuttlefish_host",
         "cuttlefish_libicuuc",
+        "cvd_cc_defaults",
     ],
 }
diff --git a/host/commands/run_cvd/boot_state_machine.cc b/host/commands/run_cvd/boot_state_machine.cc
index 64eb893..5923631 100644
--- a/host/commands/run_cvd/boot_state_machine.cc
+++ b/host/commands/run_cvd/boot_state_machine.cc
@@ -16,33 +16,221 @@
 
 #include "host/commands/run_cvd/boot_state_machine.h"
 
+#include <poll.h>
+
 #include <memory>
 #include <thread>
 
-#include "android-base/logging.h"
+#include <android-base/logging.h>
+#include <gflags/gflags.h>
+
 #include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/tee_logging.h"
 #include "host/commands/kernel_log_monitor/kernel_log_server.h"
 #include "host/commands/kernel_log_monitor/utils.h"
 #include "host/commands/run_cvd/runner_defs.h"
+#include "host/libs/config/feature.h"
+
+DEFINE_int32(reboot_notification_fd, -1,
+             "A file descriptor to notify when boot completes.");
 
 namespace cuttlefish {
+namespace {
 
-CvdBootStateMachine::CvdBootStateMachine(SharedFD fg_launcher_pipe,
-                                         SharedFD reboot_notification,
-                                         SharedFD boot_events_pipe)
-    : fg_launcher_pipe_(fg_launcher_pipe),
-      reboot_notification_(reboot_notification),
-      state_(kBootStarted) {
-  boot_event_handler_ = std::thread([this, boot_events_pipe]() {
+// Forks and returns the write end of a pipe to the child process. The parent
+// process waits for boot events to come through the pipe and exits accordingly.
+SharedFD DaemonizeLauncher(const CuttlefishConfig& config) {
+  auto instance = config.ForDefaultInstance();
+  SharedFD read_end, write_end;
+  if (!SharedFD::Pipe(&read_end, &write_end)) {
+    LOG(ERROR) << "Unable to create pipe";
+    return {};  // a closed FD
+  }
+  auto pid = fork();
+  if (pid) {
+    // Explicitly close here, otherwise we may end up reading forever if the
+    // child process dies.
+    write_end->Close();
+    RunnerExitCodes exit_code;
+    auto bytes_read = read_end->Read(&exit_code, sizeof(exit_code));
+    if (bytes_read != sizeof(exit_code)) {
+      LOG(ERROR) << "Failed to read a complete exit code, read " << bytes_read
+                 << " bytes only instead of the expected " << sizeof(exit_code);
+      exit_code = RunnerExitCodes::kPipeIOError;
+    } else if (exit_code == RunnerExitCodes::kSuccess) {
+      LOG(INFO) << "Virtual device booted successfully";
+    } else if (exit_code == RunnerExitCodes::kVirtualDeviceBootFailed) {
+      LOG(ERROR) << "Virtual device failed to boot";
+    } else {
+      LOG(ERROR) << "Unexpected exit code: " << exit_code;
+    }
+    if (exit_code == RunnerExitCodes::kSuccess) {
+      LOG(INFO) << kBootCompletedMessage;
+    } else {
+      LOG(INFO) << kBootFailedMessage;
+    }
+    std::exit(exit_code);
+  } else {
+    // The child returns the write end of the pipe
+    if (daemon(/*nochdir*/ 1, /*noclose*/ 1) != 0) {
+      LOG(ERROR) << "Failed to daemonize child process: " << strerror(errno);
+      std::exit(RunnerExitCodes::kDaemonizationError);
+    }
+    // Redirect standard I/O
+    auto log_path = instance.launcher_log_path();
+    auto log = SharedFD::Open(log_path.c_str(), O_CREAT | O_WRONLY | O_APPEND,
+                              S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+    if (!log->IsOpen()) {
+      LOG(ERROR) << "Failed to create launcher log file: " << log->StrError();
+      std::exit(RunnerExitCodes::kDaemonizationError);
+    }
+    ::android::base::SetLogger(
+        TeeLogger({{LogFileSeverity(), log, MetadataLevel::FULL}}));
+    auto dev_null = SharedFD::Open("/dev/null", O_RDONLY);
+    if (!dev_null->IsOpen()) {
+      LOG(ERROR) << "Failed to open /dev/null: " << dev_null->StrError();
+      std::exit(RunnerExitCodes::kDaemonizationError);
+    }
+    if (dev_null->UNMANAGED_Dup2(0) < 0) {
+      LOG(ERROR) << "Failed dup2 stdin: " << dev_null->StrError();
+      std::exit(RunnerExitCodes::kDaemonizationError);
+    }
+    if (log->UNMANAGED_Dup2(1) < 0) {
+      LOG(ERROR) << "Failed dup2 stdout: " << log->StrError();
+      std::exit(RunnerExitCodes::kDaemonizationError);
+    }
+    if (log->UNMANAGED_Dup2(2) < 0) {
+      LOG(ERROR) << "Failed dup2 seterr: " << log->StrError();
+      std::exit(RunnerExitCodes::kDaemonizationError);
+    }
+
+    read_end->Close();
+    return write_end;
+  }
+}
+
+class ProcessLeader : public SetupFeature {
+ public:
+  INJECT(ProcessLeader(const CuttlefishConfig& config)) : config_(config) {}
+
+  SharedFD ForegroundLauncherPipe() { return foreground_launcher_pipe_; }
+
+  // SetupFeature
+  std::string Name() const override { return "ProcessLeader"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override {
+    /* These two paths result in pretty different process state, but both
+     * achieve the same goal of making the current process the leader of a
+     * process group, and are therefore grouped together. */
+    if (config_.run_as_daemon()) {
+      foreground_launcher_pipe_ = DaemonizeLauncher(config_);
+      if (!foreground_launcher_pipe_->IsOpen()) {
+        return false;
+      }
+    } else {
+      // Make sure the launcher runs in its own process group even when running
+      // in the foreground
+      if (getsid(0) != getpid()) {
+        int retval = setpgid(0, 0);
+        if (retval) {
+          PLOG(ERROR) << "Failed to create new process group: ";
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  const CuttlefishConfig& config_;
+  SharedFD foreground_launcher_pipe_;
+};
+
+// Maintains the state of the boot process, once a final state is reached
+// (success or failure) it sends the appropriate exit code to the foreground
+// launcher process
+class CvdBootStateMachine : public SetupFeature {
+ public:
+  INJECT(CvdBootStateMachine(ProcessLeader& process_leader,
+                             KernelLogPipeProvider& kernel_log_pipe_provider))
+      : process_leader_(process_leader),
+        kernel_log_pipe_provider_(kernel_log_pipe_provider),
+        state_(kBootStarted) {}
+
+  ~CvdBootStateMachine() {
+    if (interrupt_fd_->IsOpen()) {
+      CHECK(interrupt_fd_->EventfdWrite(1) >= 0);
+    }
+    if (boot_event_handler_.joinable()) {
+      boot_event_handler_.join();
+    }
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "CvdBootStateMachine"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const {
+    return {
+        static_cast<SetupFeature*>(&process_leader_),
+        static_cast<SetupFeature*>(&kernel_log_pipe_provider_),
+    };
+  }
+  bool Setup() override {
+    interrupt_fd_ = SharedFD::Event();
+    if (!interrupt_fd_->IsOpen()) {
+      LOG(ERROR) << "Failed to open eventfd: " << interrupt_fd_->StrError();
+      return false;
+    }
+    fg_launcher_pipe_ = process_leader_.ForegroundLauncherPipe();
+    if (FLAGS_reboot_notification_fd >= 0) {
+      reboot_notification_ = SharedFD::Dup(FLAGS_reboot_notification_fd);
+      if (!reboot_notification_->IsOpen()) {
+        LOG(ERROR) << "Could not dup fd given for reboot_notification_fd";
+        return false;
+      }
+      close(FLAGS_reboot_notification_fd);
+    }
+    SharedFD boot_events_pipe = kernel_log_pipe_provider_.KernelLogPipe();
+    if (!boot_events_pipe->IsOpen()) {
+      LOG(ERROR) << "Could not get boot events pipe";
+      return false;
+    }
+    boot_event_handler_ = std::thread(
+        [this, boot_events_pipe]() { ThreadLoop(boot_events_pipe); });
+    return true;
+  }
+
+  void ThreadLoop(SharedFD boot_events_pipe) {
     while (true) {
-      SharedFDSet fd_set;
-      fd_set.Set(boot_events_pipe);
-      int result = Select(&fd_set, nullptr, nullptr, nullptr);
+      std::vector<PollSharedFd> poll_shared_fd = {
+          {
+              .fd = boot_events_pipe,
+              .events = POLLIN | POLLHUP,
+          },
+          {
+              .fd = interrupt_fd_,
+              .events = POLLIN | POLLHUP,
+          }};
+      int result = SharedFD::Poll(poll_shared_fd, -1);
+      if (poll_shared_fd[1].revents & POLLIN) {
+        return;
+      }
       if (result < 0) {
         PLOG(FATAL) << "Failed to call Select";
         return;
       }
-      if (!fd_set.IsSet(boot_events_pipe)) {
+      if (poll_shared_fd[0].revents & POLLHUP) {
+        LOG(ERROR) << "Failed to read a complete kernel log boot event.";
+        state_ |= kGuestBootFailed;
+        if (MaybeWriteNotification()) {
+          break;
+        }
+      }
+      if (!(poll_shared_fd[0].revents & POLLIN)) {
         continue;
       }
       auto sent_code = OnBootEvtReceived(boot_events_pipe);
@@ -50,61 +238,73 @@
         break;
       }
     }
-  });
-}
+  }
 
-CvdBootStateMachine::~CvdBootStateMachine() { boot_event_handler_.join(); }
+  // Returns true if the machine is left in a final state
+  bool OnBootEvtReceived(SharedFD boot_events_pipe) {
+    std::optional<monitor::ReadEventResult> read_result =
+        monitor::ReadEvent(boot_events_pipe);
+    if (!read_result) {
+      LOG(ERROR) << "Failed to read a complete kernel log boot event.";
+      state_ |= kGuestBootFailed;
+      return MaybeWriteNotification();
+    }
 
-// Returns true if the machine is left in a final state
-bool CvdBootStateMachine::OnBootEvtReceived(SharedFD boot_events_pipe) {
-  std::optional<monitor::ReadEventResult> read_result =
-      monitor::ReadEvent(boot_events_pipe);
-  if (!read_result) {
-    LOG(ERROR) << "Failed to read a complete kernel log boot event.";
-    state_ |= kGuestBootFailed;
+    if (read_result->event == monitor::Event::BootCompleted) {
+      LOG(INFO) << "Virtual device booted successfully";
+      state_ |= kGuestBootCompleted;
+    } else if (read_result->event == monitor::Event::BootFailed) {
+      LOG(ERROR) << "Virtual device failed to boot";
+      state_ |= kGuestBootFailed;
+    }  // Ignore the other signals
+
     return MaybeWriteNotification();
   }
+  bool BootCompleted() const { return state_ & kGuestBootCompleted; }
+  bool BootFailed() const { return state_ & kGuestBootFailed; }
 
-  if (read_result->event == monitor::Event::BootCompleted) {
-    LOG(INFO) << "Virtual device booted successfully";
-    state_ |= kGuestBootCompleted;
-  } else if (read_result->event == monitor::Event::BootFailed) {
-    LOG(ERROR) << "Virtual device failed to boot";
-    state_ |= kGuestBootFailed;
-  }  // Ignore the other signals
-
-  return MaybeWriteNotification();
-}
-
-bool CvdBootStateMachine::BootCompleted() const {
-  return state_ & kGuestBootCompleted;
-}
-
-bool CvdBootStateMachine::BootFailed() const {
-  return state_ & kGuestBootFailed;
-}
-
-void CvdBootStateMachine::SendExitCode(RunnerExitCodes exit_code, SharedFD fd) {
-  fd->Write(&exit_code, sizeof(exit_code));
-  // The foreground process will exit after receiving the exit code, if we try
-  // to write again we'll get a SIGPIPE
-  fd->Close();
-}
-
-bool CvdBootStateMachine::MaybeWriteNotification() {
-  std::vector<SharedFD> fds = {reboot_notification_, fg_launcher_pipe_};
-  for (auto& fd : fds) {
-    if (fd->IsOpen()) {
-      if (BootCompleted()) {
-        SendExitCode(RunnerExitCodes::kSuccess, fd);
-      } else if (state_ & kGuestBootFailed) {
-        SendExitCode(RunnerExitCodes::kVirtualDeviceBootFailed, fd);
+  void SendExitCode(RunnerExitCodes exit_code, SharedFD fd) {
+    fd->Write(&exit_code, sizeof(exit_code));
+    // The foreground process will exit after receiving the exit code, if we try
+    // to write again we'll get a SIGPIPE
+    fd->Close();
+  }
+  bool MaybeWriteNotification() {
+    std::vector<SharedFD> fds = {reboot_notification_, fg_launcher_pipe_};
+    for (auto& fd : fds) {
+      if (fd->IsOpen()) {
+        if (BootCompleted()) {
+          SendExitCode(RunnerExitCodes::kSuccess, fd);
+        } else if (state_ & kGuestBootFailed) {
+          SendExitCode(RunnerExitCodes::kVirtualDeviceBootFailed, fd);
+        }
       }
     }
+    // Either we sent the code before or just sent it, in any case the state is
+    // final
+    return BootCompleted() || (state_ & kGuestBootFailed);
   }
-  // Either we sent the code before or just sent it, in any case the state is
-  // final
-  return BootCompleted() || (state_ & kGuestBootFailed);
+
+  ProcessLeader& process_leader_;
+  KernelLogPipeProvider& kernel_log_pipe_provider_;
+
+  std::thread boot_event_handler_;
+  SharedFD fg_launcher_pipe_;
+  SharedFD reboot_notification_;
+  SharedFD interrupt_fd_;
+  int state_;
+  static const int kBootStarted = 0;
+  static const int kGuestBootCompleted = 1 << 0;
+  static const int kGuestBootFailed = 1 << 1;
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider>>
+bootStateMachineComponent() {
+  return fruit::createComponent()
+      .addMultibinding<SetupFeature, ProcessLeader>()
+      .addMultibinding<SetupFeature, CvdBootStateMachine>();
 }
 
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/boot_state_machine.h b/host/commands/run_cvd/boot_state_machine.h
index 0d02e72..796bb6f 100644
--- a/host/commands/run_cvd/boot_state_machine.h
+++ b/host/commands/run_cvd/boot_state_machine.h
@@ -15,39 +15,13 @@
  */
 #pragma once
 
-#include <memory>
-#include <thread>
-
-#include "common/libs/fs/shared_fd.h"
-#include "host/commands/run_cvd/runner_defs.h"
+#include "host/commands/run_cvd/launch.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
 
 namespace cuttlefish {
 
-// Maintains the state of the boot process, once a final state is reached
-// (success or failure) it sends the appropriate exit code to the foreground
-// launcher process
-class CvdBootStateMachine {
- public:
-  CvdBootStateMachine(SharedFD fg_launcher_pipe, SharedFD reboot_notification,
-                      SharedFD boot_events_pipe);
-  ~CvdBootStateMachine();
-
- private:
-  // Returns true if the machine is left in a final state
-  bool OnBootEvtReceived(SharedFD boot_events_pipe);
-  bool BootCompleted() const;
-  bool BootFailed() const;
-
-  void SendExitCode(RunnerExitCodes exit_code, SharedFD fd);
-  bool MaybeWriteNotification();
-
-  std::thread boot_event_handler_;
-  SharedFD fg_launcher_pipe_;
-  SharedFD reboot_notification_;
-  int state_;
-  static const int kBootStarted = 0;
-  static const int kGuestBootCompleted = 1 << 0;
-  static const int kGuestBootFailed = 1 << 1;
-};
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider>>
+bootStateMachineComponent();
 
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch.cc b/host/commands/run_cvd/launch.cc
index a6184b2..235a3ce 100644
--- a/host/commands/run_cvd/launch.cc
+++ b/host/commands/run_cvd/launch.cc
@@ -16,18 +16,30 @@
 #include "host/commands/run_cvd/launch.h"
 
 #include <android-base/logging.h>
+
+#include <unordered_set>
 #include <utility>
+#include <vector>
 
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/files.h"
+#include "common/libs/utils/network.h"
+#include "common/libs/utils/result.h"
 #include "common/libs/utils/subprocess.h"
 #include "host/commands/run_cvd/process_monitor.h"
+#include "host/commands/run_cvd/reporting.h"
 #include "host/commands/run_cvd/runner_defs.h"
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/inject.h"
 #include "host/libs/config/known_paths.h"
+#include "host/libs/vm_manager/crosvm_builder.h"
+#include "host/libs/vm_manager/crosvm_manager.h"
+#include "host/libs/vm_manager/vm_manager.h"
 
 namespace cuttlefish {
 
+using vm_manager::VmManager;
+
 namespace {
 
 template <typename T>
@@ -39,335 +51,770 @@
 
 }  // namespace
 
-KernelLogMonitorData LaunchKernelLogMonitor(
-    const CuttlefishConfig& config, unsigned int number_of_event_pipes) {
-  auto instance = config.ForDefaultInstance();
-  auto log_name = instance.kernel_log_pipe_name();
-  if (mkfifo(log_name.c_str(), 0600) != 0) {
-    LOG(ERROR) << "Unable to create named pipe at " << log_name << ": "
-               << strerror(errno);
-    return {};
+class KernelLogMonitor : public CommandSource,
+                         public KernelLogPipeProvider,
+                         public DiagnosticInformation {
+ public:
+  INJECT(KernelLogMonitor(const CuttlefishConfig::InstanceSpecific& instance))
+      : instance_(instance) {}
+
+  // DiagnosticInformation
+  std::vector<std::string> Diagnostics() const override {
+    return {"Kernel log: " + instance_.PerInstancePath("kernel.log")};
   }
 
-  SharedFD pipe;
-  // Open the pipe here (from the launcher) to ensure the pipe is not deleted
-  // due to the usage counters in the kernel reaching zero. If this is not done
-  // and the kernel_log_monitor crashes for some reason the VMM may get SIGPIPE.
-  pipe = SharedFD::Open(log_name.c_str(), O_RDWR);
-  Command command(KernelLogMonitorBinary());
-  command.AddParameter("-log_pipe_fd=", pipe);
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command command(KernelLogMonitorBinary());
+    command.AddParameter("-log_pipe_fd=", fifo_);
 
-  KernelLogMonitorData ret;
-
-  if (number_of_event_pipes > 0) {
-    command.AddParameter("-subscriber_fds=");
-    for (unsigned int i = 0; i < number_of_event_pipes; ++i) {
-      SharedFD event_pipe_write_end, event_pipe_read_end;
-      if (!SharedFD::Pipe(&event_pipe_read_end, &event_pipe_write_end)) {
-        LOG(ERROR) << "Unable to create kernel log events pipe: " << strerror(errno);
-        std::exit(RunnerExitCodes::kPipeIOError);
+    if (!event_pipe_write_ends_.empty()) {
+      command.AddParameter("-subscriber_fds=");
+      for (size_t i = 0; i < event_pipe_write_ends_.size(); i++) {
+        if (i > 0) {
+          command.AppendToLastParameter(",");
+        }
+        command.AppendToLastParameter(event_pipe_write_ends_[i]);
       }
-      if (i > 0) {
-        command.AppendToLastParameter(",");
+    }
+
+    return single_element_emplace(std::move(command));
+  }
+
+  // KernelLogPipeProvider
+  SharedFD KernelLogPipe() override {
+    CHECK(!event_pipe_read_ends_.empty()) << "No more kernel pipes left";
+    SharedFD ret = event_pipe_read_ends_.back();
+    event_pipe_read_ends_.pop_back();
+    return ret;
+  }
+
+ private:
+  // SetupFeature
+  bool Enabled() const override { return true; }
+  std::string Name() const override { return "KernelLogMonitor"; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    auto log_name = instance_.kernel_log_pipe_name();
+    CF_EXPECT(mkfifo(log_name.c_str(), 0600) == 0,
+              "Unable to create named pipe at " << log_name << ": "
+                                                << strerror(errno));
+
+    // Open the pipe here (from the launcher) to ensure the pipe is not deleted
+    // due to the usage counters in the kernel reaching zero. If this is not
+    // done and the kernel_log_monitor crashes for some reason the VMM may get
+    // SIGPIPE.
+    fifo_ = SharedFD::Open(log_name, O_RDWR);
+    CF_EXPECT(fifo_->IsOpen(),
+              "Unable to open \"" << log_name << "\": " << fifo_->StrError());
+
+    // TODO(schuffelen): Find a way to calculate this dynamically.
+    int number_of_event_pipes = 4;
+    if (number_of_event_pipes > 0) {
+      for (unsigned int i = 0; i < number_of_event_pipes; ++i) {
+        SharedFD event_pipe_write_end, event_pipe_read_end;
+        CF_EXPECT(SharedFD::Pipe(&event_pipe_read_end, &event_pipe_write_end),
+                  "Failed creating kernel log pipe: " << strerror(errno));
+        event_pipe_write_ends_.push_back(event_pipe_write_end);
+        event_pipe_read_ends_.push_back(event_pipe_read_end);
       }
-      command.AppendToLastParameter(event_pipe_write_end);
-      ret.pipes.push_back(event_pipe_read_end);
     }
-  }
-
-  ret.commands.emplace_back(std::move(command));
-
-  return ret;
-}
-
-std::vector<Command> LaunchRootCanal(const CuttlefishConfig& config) {
-  if (!config.enable_host_bluetooth()) {
     return {};
   }
 
-  auto instance = config.ForDefaultInstance();
-  Command command(RootCanalBinary());
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD fifo_;
+  std::vector<SharedFD> event_pipe_write_ends_;
+  std::vector<SharedFD> event_pipe_read_ends_;
+};
 
-  // Test port
-  command.AddParameter(instance.rootcanal_test_port());
-  // HCI server port
-  command.AddParameter(instance.rootcanal_hci_port());
-  // Link server port
-  command.AddParameter(instance.rootcanal_link_port());
-  // Bluetooth controller properties file
-  command.AddParameter("--controller_properties_file=",
-                       instance.rootcanal_config_file());
-  // Default commands file
-  command.AddParameter("--default_commands_file=",
-                       instance.rootcanal_default_commands_file());
+class LogTeeCreator {
+ public:
+  INJECT(LogTeeCreator(const CuttlefishConfig::InstanceSpecific& instance))
+      : instance_(instance) {}
 
-  return single_element_emplace(std::move(command));
-}
+  Command CreateLogTee(Command& cmd, const std::string& process_name) {
+    auto name_with_ext = process_name + "_logs.fifo";
+    auto logs_path = instance_.PerInstanceInternalPath(name_with_ext.c_str());
+    auto logs = SharedFD::Fifo(logs_path, 0666);
+    if (!logs->IsOpen()) {
+      LOG(FATAL) << "Failed to create fifo for " << process_name
+                 << " output: " << logs->StrError();
+    }
 
-std::vector<Command> LaunchLogcatReceiver(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  auto log_name = instance.logcat_pipe_name();
-  if (mkfifo(log_name.c_str(), 0600) != 0) {
-    LOG(ERROR) << "Unable to create named pipe at " << log_name << ": "
-               << strerror(errno);
-    return {};
+    cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, logs);
+    cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, logs);
+
+    return Command(HostBinaryPath("log_tee"))
+        .AddParameter("--process_name=", process_name)
+        .AddParameter("--log_fd_in=", logs);
   }
 
-  SharedFD pipe;
-  // Open the pipe here (from the launcher) to ensure the pipe is not deleted
-  // due to the usage counters in the kernel reaching zero. If this is not done
-  // and the logcat_receiver crashes for some reason the VMM may get SIGPIPE.
-  pipe = SharedFD::Open(log_name.c_str(), O_RDWR);
-  Command command(LogcatReceiverBinary());
-  command.AddParameter("-log_pipe_fd=", pipe);
+ private:
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
 
-  return single_element_emplace(std::move(command));
-}
+class RootCanal : public CommandSource {
+ public:
+  INJECT(RootCanal(const CuttlefishConfig& config,
+                   const CuttlefishConfig::InstanceSpecific& instance,
+                   LogTeeCreator& log_tee))
+      : config_(config), instance_(instance), log_tee_(log_tee) {}
 
-std::vector<Command> LaunchConfigServer(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  auto port = instance.config_server_port();
-  auto socket = SharedFD::VsockServer(port, SOCK_STREAM);
-  if (!socket->IsOpen()) {
-    LOG(ERROR) << "Unable to create configuration server socket: "
-               << socket->StrError();
-    std::exit(RunnerExitCodes::kConfigServerError);
-  }
-  Command cmd(ConfigServerBinary());
-  cmd.AddParameter("-server_fd=", socket);
-  return single_element_emplace(std::move(cmd));
-}
-
-std::vector<Command> LaunchTombstoneReceiver(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-
-  std::string tombstoneDir = instance.PerInstancePath("tombstones");
-  if (!DirectoryExists(tombstoneDir.c_str())) {
-    LOG(DEBUG) << "Setting up " << tombstoneDir;
-    if (mkdir(tombstoneDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) <
-        0) {
-      LOG(ERROR) << "Failed to create tombstone directory: " << tombstoneDir
-                 << ". Error: " << errno;
-      exit(RunnerExitCodes::kTombstoneDirCreationError);
+  // CommandSource
+  std::vector<Command> Commands() override {
+    if (!Enabled()) {
       return {};
     }
+    Command command(RootCanalBinary());
+
+    // Test port
+    command.AddParameter(config_.rootcanal_test_port());
+    // HCI server port
+    command.AddParameter(config_.rootcanal_hci_port());
+    // Link server port
+    command.AddParameter(config_.rootcanal_link_port());
+    // Bluetooth controller properties file
+    command.AddParameter("--controller_properties_file=",
+                         config_.rootcanal_config_file());
+    // Default commands file
+    command.AddParameter("--default_commands_file=",
+                         config_.rootcanal_default_commands_file());
+
+    std::vector<Command> commands;
+    commands.emplace_back(log_tee_.CreateLogTee(command, "rootcanal"));
+    commands.emplace_back(std::move(command));
+    return commands;
   }
 
-  auto port = instance.tombstone_receiver_port();
-  auto socket = SharedFD::VsockServer(port, SOCK_STREAM);
-  if (!socket->IsOpen()) {
-    LOG(ERROR) << "Unable to create tombstone server socket: "
-               << socket->StrError();
-    std::exit(RunnerExitCodes::kTombstoneServerError);
-    return {};
+  // SetupFeature
+  std::string Name() const override { return "RootCanal"; }
+  bool Enabled() const override {
+    return config_.enable_host_bluetooth() && instance_.start_rootcanal();
   }
-  Command cmd(TombstoneReceiverBinary());
-  cmd.AddParameter("-server_fd=", socket);
-  cmd.AddParameter("-tombstone_dir=", tombstoneDir);
 
-  return single_element_emplace(std::move(cmd));
-}
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override { return true; }
 
-std::vector<Command> LaunchMetrics() {
-  return single_element_emplace(Command(MetricsBinary()));
-}
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  LogTeeCreator& log_tee_;
+};
 
-std::vector<Command> LaunchGnssGrpcProxyServerIfEnabled(
-    const CuttlefishConfig& config) {
-  if (!config.enable_gnss_grpc_proxy() || !FileExists(GnssGrpcProxyBinary())) {
+class LogcatReceiver : public CommandSource, public DiagnosticInformation {
+ public:
+  INJECT(LogcatReceiver(const CuttlefishConfig::InstanceSpecific& instance))
+      : instance_(instance) {}
+  // DiagnosticInformation
+  std::vector<std::string> Diagnostics() const override {
+    return {"Logcat output: " + instance_.logcat_path()};
+  }
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    return single_element_emplace(
+        Command(LogcatReceiverBinary()).AddParameter("-log_pipe_fd=", pipe_));
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "LogcatReceiver"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() {
+    auto log_name = instance_.logcat_pipe_name();
+    CF_EXPECT(mkfifo(log_name.c_str(), 0600) == 0,
+              "Unable to create named pipe at " << log_name << ": "
+                                                << strerror(errno));
+    // Open the pipe here (from the launcher) to ensure the pipe is not deleted
+    // due to the usage counters in the kernel reaching zero. If this is not
+    // done and the logcat_receiver crashes for some reason the VMM may get
+    // SIGPIPE.
+    pipe_ = SharedFD::Open(log_name.c_str(), O_RDWR);
+    CF_EXPECT(pipe_->IsOpen(),
+              "Can't open \"" << log_name << "\": " << pipe_->StrError());
     return {};
   }
 
-  Command gnss_grpc_proxy_cmd(GnssGrpcProxyBinary());
-  auto instance = config.ForDefaultInstance();
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD pipe_;
+};
 
-  auto gnss_in_pipe_name = instance.gnss_in_pipe_name();
-  if (mkfifo(gnss_in_pipe_name.c_str(), 0600) != 0) {
-    auto error = errno;
-    LOG(ERROR) << "Failed to create gnss input fifo for crosvm: "
-               << strerror(error);
+class ConfigServer : public CommandSource {
+ public:
+  INJECT(ConfigServer(const CuttlefishConfig::InstanceSpecific& instance))
+      : instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    return single_element_emplace(
+        Command(ConfigServerBinary()).AddParameter("-server_fd=", socket_));
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "ConfigServer"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    auto port = instance_.config_server_port();
+    socket_ = SharedFD::VsockServer(port, SOCK_STREAM);
+    CF_EXPECT(socket_->IsOpen(),
+              "Unable to create configuration server socket: "
+                  << socket_->StrError());
     return {};
   }
 
-  auto gnss_out_pipe_name = instance.gnss_out_pipe_name();
-  if (mkfifo(gnss_out_pipe_name.c_str(), 0660) != 0) {
-    auto error = errno;
-    LOG(ERROR) << "Failed to create gnss output fifo for crosvm: "
-               << strerror(error);
+ private:
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD socket_;
+};
+
+class TombstoneReceiver : public CommandSource {
+ public:
+  INJECT(TombstoneReceiver(const CuttlefishConfig::InstanceSpecific& instance))
+      : instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    return single_element_emplace(
+        Command(TombstoneReceiverBinary())
+            .AddParameter("-server_fd=", socket_)
+            .AddParameter("-tombstone_dir=", tombstone_dir_));
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "TombstoneReceiver"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    tombstone_dir_ = instance_.PerInstancePath("tombstones");
+    if (!DirectoryExists(tombstone_dir_)) {
+      LOG(DEBUG) << "Setting up " << tombstone_dir_;
+      CF_EXPECT(mkdir(tombstone_dir_.c_str(),
+                      S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0,
+                "Failed to create tombstone directory: "
+                    << tombstone_dir_ << ". Error: " << strerror(errno));
+    }
+
+    auto port = instance_.tombstone_receiver_port();
+    socket_ = SharedFD::VsockServer(port, SOCK_STREAM);
+    CF_EXPECT(socket_->IsOpen(), "Unable to create tombstone server socket: "
+                                     << socket_->StrError());
     return {};
   }
 
-  // These fds will only be read from or written to, but open them with
-  // read and write access to keep them open in case the subprocesses exit
-  SharedFD gnss_grpc_proxy_in_wr =
-      SharedFD::Open(gnss_in_pipe_name.c_str(), O_RDWR);
-  if (!gnss_grpc_proxy_in_wr->IsOpen()) {
-    LOG(ERROR) << "Failed to open gnss_grpc_proxy input fifo for writes: "
-               << gnss_grpc_proxy_in_wr->StrError();
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD socket_;
+  std::string tombstone_dir_;
+};
+
+class MetricsService : public CommandSource {
+ public:
+  INJECT(MetricsService(const CuttlefishConfig& config)) : config_(config) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    return single_element_emplace(Command(MetricsBinary()));
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "MetricsService"; }
+  bool Enabled() const override {
+    return config_.enable_metrics() == CuttlefishConfig::kYes;
+  }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override { return true; }
+
+ private:
+  const CuttlefishConfig& config_;
+};
+
+class GnssGrpcProxyServer : public CommandSource {
+ public:
+  INJECT(
+      GnssGrpcProxyServer(const CuttlefishConfig& config,
+                          const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command gnss_grpc_proxy_cmd(GnssGrpcProxyBinary());
+    const unsigned gnss_grpc_proxy_server_port =
+        instance_.gnss_grpc_proxy_server_port();
+    gnss_grpc_proxy_cmd.AddParameter("--gnss_in_fd=", gnss_grpc_proxy_in_wr_);
+    gnss_grpc_proxy_cmd.AddParameter("--gnss_out_fd=", gnss_grpc_proxy_out_rd_);
+    gnss_grpc_proxy_cmd.AddParameter("--gnss_grpc_port=",
+                                     gnss_grpc_proxy_server_port);
+    if (!instance_.gnss_file_path().empty()) {
+      // If path is provided, proxy will start as local mode.
+      gnss_grpc_proxy_cmd.AddParameter("--gnss_file_path=",
+                                       instance_.gnss_file_path());
+    }
+    return single_element_emplace(std::move(gnss_grpc_proxy_cmd));
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "GnssGrpcProxyServer"; }
+  bool Enabled() const override {
+    return config_.enable_gnss_grpc_proxy() &&
+           FileExists(GnssGrpcProxyBinary());
+  }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    std::vector<SharedFD> fifos;
+    std::vector<std::string> fifo_paths = {
+        instance_.PerInstanceInternalPath("gnsshvc_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("gnsshvc_fifo_vm.out"),
+        instance_.PerInstanceInternalPath("locationhvc_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("locationhvc_fifo_vm.out"),
+    };
+    for (const auto& path : fifo_paths) {
+      unlink(path.c_str());
+      CF_EXPECT(mkfifo(path.c_str(), 0660) == 0, "Could not create " << path);
+      auto fd = SharedFD::Open(path, O_RDWR);
+      CF_EXPECT(fd->IsOpen(),
+                "Could not open " << path << ": " << fd->StrError());
+      fifos.push_back(fd);
+    }
+
+    gnss_grpc_proxy_in_wr_ = fifos[0];
+    gnss_grpc_proxy_out_rd_ = fifos[1];
     return {};
   }
 
-  SharedFD gnss_grpc_proxy_out_rd =
-      SharedFD::Open(gnss_out_pipe_name.c_str(), O_RDWR);
-  if (!gnss_grpc_proxy_out_rd->IsOpen()) {
-    LOG(ERROR) << "Failed to open gnss_grpc_proxy output fifo for reads: "
-               << gnss_grpc_proxy_out_rd->StrError();
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD gnss_grpc_proxy_in_wr_;
+  SharedFD gnss_grpc_proxy_out_rd_;
+};
+
+class BluetoothConnector : public CommandSource {
+ public:
+  INJECT(BluetoothConnector(const CuttlefishConfig& config,
+                            const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command command(HostBinaryPath("bt_connector"));
+    command.AddParameter("-bt_out=", fifos_[0]);
+    command.AddParameter("-bt_in=", fifos_[1]);
+    command.AddParameter("-hci_port=", config_.rootcanal_hci_port());
+    command.AddParameter("-link_port=", config_.rootcanal_link_port());
+    command.AddParameter("-test_port=", config_.rootcanal_test_port());
+    return single_element_emplace(std::move(command));
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "BluetoothConnector"; }
+  bool Enabled() const override { return config_.enable_host_bluetooth(); }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() {
+    std::vector<std::string> fifo_paths = {
+        instance_.PerInstanceInternalPath("bt_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("bt_fifo_vm.out"),
+    };
+    for (const auto& path : fifo_paths) {
+      unlink(path.c_str());
+      CF_EXPECT(mkfifo(path.c_str(), 0660) == 0, "Could not create " << path);
+      auto fd = SharedFD::Open(path, O_RDWR);
+      CF_EXPECT(fd->IsOpen(),
+                "Could not open " << path << ": " << fd->StrError());
+      fifos_.push_back(fd);
+    }
     return {};
   }
 
-  const unsigned gnss_grpc_proxy_server_port =
-      instance.gnss_grpc_proxy_server_port();
-  gnss_grpc_proxy_cmd.AddParameter("--gnss_in_fd=", gnss_grpc_proxy_in_wr);
-  gnss_grpc_proxy_cmd.AddParameter("--gnss_out_fd=", gnss_grpc_proxy_out_rd);
-  gnss_grpc_proxy_cmd.AddParameter("--gnss_grpc_port=",
-                                   gnss_grpc_proxy_server_port);
-  if (!instance.gnss_file_path().empty()) {
-    // If path is provided, proxy will start as local mode.
-    gnss_grpc_proxy_cmd.AddParameter("--gnss_file_path=",
-                                     instance.gnss_file_path());
-  }
-  return single_element_emplace(std::move(gnss_grpc_proxy_cmd));
-}
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  std::vector<SharedFD> fifos_;
+};
 
-std::vector<Command> LaunchBluetoothConnector(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  std::vector<std::string> fifo_paths = {
-      instance.PerInstanceInternalPath("bt_fifo_vm.in"),
-      instance.PerInstanceInternalPath("bt_fifo_vm.out"),
-  };
-  std::vector<SharedFD> fifos;
-  for (const auto& path : fifo_paths) {
-    unlink(path.c_str());
-    if (mkfifo(path.c_str(), 0660) < 0) {
-      PLOG(ERROR) << "Could not create " << path;
+class SecureEnvironment : public CommandSource {
+ public:
+  INJECT(SecureEnvironment(const CuttlefishConfig& config,
+                           const CuttlefishConfig::InstanceSpecific& instance,
+                           KernelLogPipeProvider& kernel_log_pipe_provider))
+      : config_(config),
+        instance_(instance),
+        kernel_log_pipe_provider_(kernel_log_pipe_provider) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command command(HostBinaryPath("secure_env"));
+    command.AddParameter("-confui_server_fd=", confui_server_fd_);
+    command.AddParameter("-keymaster_fd_out=", fifos_[0]);
+    command.AddParameter("-keymaster_fd_in=", fifos_[1]);
+    command.AddParameter("-gatekeeper_fd_out=", fifos_[2]);
+    command.AddParameter("-gatekeeper_fd_in=", fifos_[3]);
+
+    const auto& secure_hals = config_.secure_hals();
+    bool secure_keymint = secure_hals.count(SecureHal::Keymint) > 0;
+    command.AddParameter("-keymint_impl=", secure_keymint ? "tpm" : "software");
+    bool secure_gatekeeper = secure_hals.count(SecureHal::Gatekeeper) > 0;
+    auto gatekeeper_impl = secure_gatekeeper ? "tpm" : "software";
+    command.AddParameter("-gatekeeper_impl=", gatekeeper_impl);
+
+    command.AddParameter("-kernel_events_fd=", kernel_log_pipe_);
+
+    return single_element_emplace(std::move(command));
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "SecureEnvironment"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override {
+    return {&kernel_log_pipe_provider_};
+  }
+  Result<void> ResultSetup() override {
+    std::vector<std::string> fifo_paths = {
+        instance_.PerInstanceInternalPath("keymaster_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("keymaster_fifo_vm.out"),
+        instance_.PerInstanceInternalPath("gatekeeper_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
+    };
+    std::vector<SharedFD> fifos;
+    for (const auto& path : fifo_paths) {
+      unlink(path.c_str());
+      CF_EXPECT(mkfifo(path.c_str(), 0660) == 0, "Could not create " << path);
+      auto fd = SharedFD::Open(path, O_RDWR);
+      CF_EXPECT(fd->IsOpen(),
+                "Could not open " << path << ": " << fd->StrError());
+      fifos_.push_back(fd);
+    }
+
+    auto confui_socket_path =
+        instance_.PerInstanceInternalPath("confui_sign.sock");
+    confui_server_fd_ = SharedFD::SocketLocalServer(confui_socket_path, false,
+                                                    SOCK_STREAM, 0600);
+    CF_EXPECT(confui_server_fd_->IsOpen(),
+              "Could not open " << confui_socket_path << ": "
+                                << confui_server_fd_->StrError());
+    kernel_log_pipe_ = kernel_log_pipe_provider_.KernelLogPipe();
+
+    return {};
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD confui_server_fd_;
+  std::vector<SharedFD> fifos_;
+  KernelLogPipeProvider& kernel_log_pipe_provider_;
+  SharedFD kernel_log_pipe_;
+};
+
+class VehicleHalServer : public CommandSource {
+ public:
+  INJECT(VehicleHalServer(const CuttlefishConfig& config,
+                          const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command grpc_server(VehicleHalGrpcServerBinary());
+
+    const unsigned vhal_server_cid = 2;
+    const unsigned vhal_server_port = instance_.vehicle_hal_server_port();
+    const std::string vhal_server_power_state_file =
+        AbsolutePath(instance_.PerInstancePath("power_state"));
+    const std::string vhal_server_power_state_socket =
+        AbsolutePath(instance_.PerInstancePath("power_state_socket"));
+
+    grpc_server.AddParameter("--server_cid=", vhal_server_cid);
+    grpc_server.AddParameter("--server_port=", vhal_server_port);
+    grpc_server.AddParameter("--power_state_file=",
+                             vhal_server_power_state_file);
+    grpc_server.AddParameter("--power_state_socket=",
+                             vhal_server_power_state_socket);
+    return single_element_emplace(std::move(grpc_server));
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "VehicleHalServer"; }
+  bool Enabled() const override {
+    return config_.enable_vehicle_hal_grpc_server() &&
+           FileExists(VehicleHalGrpcServerBinary());
+  }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override { return true; }
+
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class ConsoleForwarder : public CommandSource, public DiagnosticInformation {
+ public:
+  INJECT(ConsoleForwarder(const CuttlefishConfig& config,
+                          const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+  // DiagnosticInformation
+  std::vector<std::string> Diagnostics() const override {
+    if (Enabled()) {
+      return {"To access the console run: screen " + instance_.console_path()};
+    } else {
+      return {"Serial console is disabled; use -console=true to enable it."};
+    }
+  }
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command console_forwarder_cmd(ConsoleForwarderBinary());
+
+    console_forwarder_cmd.AddParameter("--console_in_fd=",
+                                       console_forwarder_in_wr_);
+    console_forwarder_cmd.AddParameter("--console_out_fd=",
+                                       console_forwarder_out_rd_);
+    return single_element_emplace(std::move(console_forwarder_cmd));
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "ConsoleForwarder"; }
+  bool Enabled() const override { return config_.console(); }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    auto console_in_pipe_name = instance_.console_in_pipe_name();
+    CF_EXPECT(
+        mkfifo(console_in_pipe_name.c_str(), 0600) == 0,
+        "Failed to create console input fifo for crosvm: " << strerror(errno));
+
+    auto console_out_pipe_name = instance_.console_out_pipe_name();
+    CF_EXPECT(
+        mkfifo(console_out_pipe_name.c_str(), 0660) == 0,
+        "Failed to create console output fifo for crosvm: " << strerror(errno));
+
+    // These fds will only be read from or written to, but open them with
+    // read and write access to keep them open in case the subprocesses exit
+    console_forwarder_in_wr_ =
+        SharedFD::Open(console_in_pipe_name.c_str(), O_RDWR);
+    CF_EXPECT(console_forwarder_in_wr_->IsOpen(),
+              "Failed to open console_forwarder input fifo for writes: "
+                  << console_forwarder_in_wr_->StrError());
+
+    console_forwarder_out_rd_ =
+        SharedFD::Open(console_out_pipe_name.c_str(), O_RDWR);
+    CF_EXPECT(console_forwarder_out_rd_->IsOpen(),
+              "Failed to open console_forwarder output fifo for reads: "
+                  << console_forwarder_out_rd_->StrError());
+    return {};
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD console_forwarder_in_wr_;
+  SharedFD console_forwarder_out_rd_;
+};
+
+class WmediumdServer : public CommandSource {
+ public:
+  INJECT(WmediumdServer(const CuttlefishConfig& config,
+                        const CuttlefishConfig::InstanceSpecific& instance,
+                        LogTeeCreator& log_tee))
+      : config_(config), instance_(instance), log_tee_(log_tee) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command cmd(WmediumdBinary());
+    cmd.AddParameter("-u", config_.vhost_user_mac80211_hwsim());
+    cmd.AddParameter("-a", config_.wmediumd_api_server_socket());
+    cmd.AddParameter("-c", config_path_);
+
+    std::vector<Command> commands;
+    commands.emplace_back(log_tee_.CreateLogTee(cmd, "wmediumd"));
+    commands.emplace_back(std::move(cmd));
+    return commands;
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "WmediumdServer"; }
+  bool Enabled() const override {
+#ifndef ENFORCE_MAC80211_HWSIM
+    return false;
+#else
+    return instance_.start_wmediumd();
+#endif
+  }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    // If wmediumd configuration is given, use it
+    if (!config_.wmediumd_config().empty()) {
+      config_path_ = config_.wmediumd_config();
       return {};
     }
-    auto fd = SharedFD::Open(path, O_RDWR);
-    if (!fd->IsOpen()) {
-      LOG(ERROR) << "Could not open " << path << ": " << fd->StrError();
-      return {};
+    // Otherwise, generate wmediumd configuration using the current wifi mac
+    // prefix before start
+    config_path_ = instance_.PerInstanceInternalPath("wmediumd.cfg");
+    Command gen_config_cmd(WmediumdGenConfigBinary());
+    gen_config_cmd.AddParameter("-o", config_path_);
+    gen_config_cmd.AddParameter("-p", instance_.wifi_mac_prefix());
+
+    int success = gen_config_cmd.Start().Wait();
+    CF_EXPECT(success == 0, "Unable to run " << gen_config_cmd.Executable()
+                                             << ". Exited with status "
+                                             << success);
+    return {};
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  LogTeeCreator& log_tee_;
+  std::string config_path_;
+};
+
+class VmmCommands : public CommandSource {
+ public:
+  INJECT(VmmCommands(const CuttlefishConfig& config, VmManager& vmm))
+      : config_(config), vmm_(vmm) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    return vmm_.StartCommands(config_);
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "VirtualMachineManager"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override { return true; }
+
+  const CuttlefishConfig& config_;
+  VmManager& vmm_;
+};
+
+class OpenWrt : public CommandSource {
+ public:
+  INJECT(OpenWrt(const CuttlefishConfig& config,
+                 const CuttlefishConfig::InstanceSpecific& instance,
+                 LogTeeCreator& log_tee))
+      : config_(config), instance_(instance), log_tee_(log_tee) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    constexpr auto crosvm_for_ap_socket = "ap_control.sock";
+
+    CrosvmBuilder ap_cmd;
+    ap_cmd.SetBinary(config_.crosvm_binary());
+    ap_cmd.AddControlSocket(
+        instance_.PerInstanceInternalPath(crosvm_for_ap_socket));
+
+    if (!config_.vhost_user_mac80211_hwsim().empty()) {
+      ap_cmd.Cmd().AddParameter("--vhost-user-mac80211-hwsim=",
+                                config_.vhost_user_mac80211_hwsim());
     }
-    fifos.push_back(fd);
-  }
-
-  Command command(DefaultHostArtifactsPath("bin/bt_connector"));
-  command.AddParameter("-bt_out=", fifos[0]);
-  command.AddParameter("-bt_in=", fifos[1]);
-  command.AddParameter("-hci_port=", instance.rootcanal_hci_port());
-  command.AddParameter("-link_port=", instance.rootcanal_link_port());
-  command.AddParameter("-test_port=", instance.rootcanal_test_port());
-  return single_element_emplace(std::move(command));
-}
-
-std::vector<Command> LaunchSecureEnvironment(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  std::vector<std::string> fifo_paths = {
-    instance.PerInstanceInternalPath("keymaster_fifo_vm.in"),
-    instance.PerInstanceInternalPath("keymaster_fifo_vm.out"),
-    instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"),
-    instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
-  };
-  std::vector<SharedFD> fifos;
-  for (const auto& path : fifo_paths) {
-    unlink(path.c_str());
-    if (mkfifo(path.c_str(), 0600) < 0) {
-      PLOG(ERROR) << "Could not create " << path;
-      return {};
+    SharedFD wifi_tap = ap_cmd.AddTap(instance_.wifi_tap_name());
+    // Only run the leases workaround if we are not using the new network
+    // bridge architecture - in that case, we have a wider DHCP address
+    // space and stale leases should be much less of an issue
+    if (!FileExists("/var/run/cuttlefish-dnsmasq-cvd-wbr.leases") &&
+        wifi_tap->IsOpen()) {
+      // TODO(schuffelen): QEMU also needs this and this is not the best place
+      // for this code. Find a better place to put it.
+      auto lease_file =
+          ForCurrentInstance("/var/run/cuttlefish-dnsmasq-cvd-wbr-") +
+          ".leases";
+      std::uint8_t dhcp_server_ip[] = {
+          192, 168, 96, (std::uint8_t)(ForCurrentInstance(1) * 4 - 3)};
+      if (!ReleaseDhcpLeases(lease_file, wifi_tap, dhcp_server_ip)) {
+        LOG(ERROR)
+            << "Failed to release wifi DHCP leases. Connecting to the wifi "
+            << "network may not work.";
+      }
     }
-    auto fd = SharedFD::Open(path, O_RDWR);
-    if (!fd->IsOpen()) {
-      LOG(ERROR) << "Could not open " << path << ": " << fd->StrError();
-      return {};
+    if (config_.enable_sandbox()) {
+      ap_cmd.Cmd().AddParameter("--seccomp-policy-dir=",
+                                config_.seccomp_policy_dir());
+    } else {
+      ap_cmd.Cmd().AddParameter("--disable-sandbox");
     }
-    fifos.push_back(fd);
+    ap_cmd.Cmd().AddParameter("--rwdisk=",
+                              instance_.PerInstancePath("ap_overlay.img"));
+    ap_cmd.Cmd().AddParameter(
+        "--disk=", instance_.PerInstancePath("persistent_composite.img"));
+    ap_cmd.Cmd().AddParameter("--params=\"root=" + config_.ap_image_dev_path() +
+                              "\"");
+
+    auto kernel_logs_path = instance_.PerInstanceLogPath("crosvm_openwrt.log");
+    ap_cmd.AddSerialConsoleReadOnly(kernel_logs_path);
+
+    ap_cmd.Cmd().AddParameter(config_.ap_kernel_image());
+
+    std::vector<Command> commands;
+    commands.emplace_back(log_tee_.CreateLogTee(ap_cmd.Cmd(), "openwrt"));
+    commands.emplace_back(std::move(ap_cmd.Cmd()));
+    return commands;
   }
 
-  Command command(HostBinaryPath("secure_env"));
-  command.AddParameter("-keymaster_fd_out=", fifos[0]);
-  command.AddParameter("-keymaster_fd_in=", fifos[1]);
-  command.AddParameter("-gatekeeper_fd_out=", fifos[2]);
-  command.AddParameter("-gatekeeper_fd_in=", fifos[3]);
+  // SetupFeature
+  std::string Name() const override { return "OpenWrt"; }
+  bool Enabled() const override {
+#ifndef ENFORCE_MAC80211_HWSIM
+    return false;
+#else
+    return instance_.start_ap() &&
+           config_.vm_manager() == vm_manager::CrosvmManager::name();
+#endif
+  }
 
-  const auto& secure_hals = config.secure_hals();
-  bool secure_keymint = secure_hals.count(SecureHal::Keymint) > 0;
-  command.AddParameter("-keymint_impl=", secure_keymint ? "tpm" : "software");
-  bool secure_gatekeeper = secure_hals.count(SecureHal::Gatekeeper) > 0;
-  auto gatekeeper_impl = secure_gatekeeper ? "tpm" : "software";
-  command.AddParameter("-gatekeeper_impl=", gatekeeper_impl);
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override { return true; }
 
-  return single_element_emplace(std::move(command));
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  LogTeeCreator& log_tee_;
+};
+
+using PublicDeps = fruit::Required<const CuttlefishConfig, VmManager,
+                                   const CuttlefishConfig::InstanceSpecific>;
+fruit::Component<PublicDeps, KernelLogPipeProvider> launchComponent() {
+  using InternalDeps = fruit::Required<const CuttlefishConfig, VmManager,
+                                       const CuttlefishConfig::InstanceSpecific,
+                                       KernelLogPipeProvider>;
+  using Multi = Multibindings<InternalDeps>;
+  using Bases =
+      Multi::Bases<CommandSource, DiagnosticInformation, SetupFeature>;
+  return fruit::createComponent()
+      .bind<KernelLogPipeProvider, KernelLogMonitor>()
+      .install(Bases::Impls<BluetoothConnector>)
+      .install(Bases::Impls<ConfigServer>)
+      .install(Bases::Impls<ConsoleForwarder>)
+      .install(Bases::Impls<GnssGrpcProxyServer>)
+      .install(Bases::Impls<KernelLogMonitor>)
+      .install(Bases::Impls<LogcatReceiver>)
+      .install(Bases::Impls<MetricsService>)
+      .install(Bases::Impls<RootCanal>)
+      .install(Bases::Impls<SecureEnvironment>)
+      .install(Bases::Impls<TombstoneReceiver>)
+      .install(Bases::Impls<VehicleHalServer>)
+      .install(Bases::Impls<VmmCommands>)
+      .install(Bases::Impls<WmediumdServer>)
+      .install(Bases::Impls<OpenWrt>);
 }
 
-std::vector<Command> LaunchVehicleHalServerIfEnabled(
-    const CuttlefishConfig& config) {
-  if (!config.enable_vehicle_hal_grpc_server() ||
-    !FileExists(config.vehicle_hal_grpc_server_binary())) {
-    return {};
-  }
-
-  Command grpc_server(config.vehicle_hal_grpc_server_binary());
-  auto instance = config.ForDefaultInstance();
-
-  const unsigned vhal_server_cid = 2;
-  const unsigned vhal_server_port = instance.vehicle_hal_server_port();
-  const std::string vhal_server_power_state_file =
-      AbsolutePath(instance.PerInstancePath("power_state"));
-  const std::string vhal_server_power_state_socket =
-      AbsolutePath(instance.PerInstancePath("power_state_socket"));
-
-  grpc_server.AddParameter("--server_cid=", vhal_server_cid);
-  grpc_server.AddParameter("--server_port=", vhal_server_port);
-  grpc_server.AddParameter("--power_state_file=", vhal_server_power_state_file);
-  grpc_server.AddParameter("--power_state_socket=", vhal_server_power_state_socket);
-  return single_element_emplace(std::move(grpc_server));
-}
-
-std::vector<Command> LaunchConsoleForwarderIfEnabled(
-    const CuttlefishConfig& config) {
-  if (!config.console()) {
-    return {};
-  }
-
-  Command console_forwarder_cmd(ConsoleForwarderBinary());
-  auto instance = config.ForDefaultInstance();
-
-  auto console_in_pipe_name = instance.console_in_pipe_name();
-  if (mkfifo(console_in_pipe_name.c_str(), 0600) != 0) {
-    auto error = errno;
-    LOG(ERROR) << "Failed to create console input fifo for crosvm: "
-               << strerror(error);
-    return {};
-  }
-
-  auto console_out_pipe_name = instance.console_out_pipe_name();
-  if (mkfifo(console_out_pipe_name.c_str(), 0660) != 0) {
-    auto error = errno;
-    LOG(ERROR) << "Failed to create console output fifo for crosvm: "
-               << strerror(error);
-    return {};
-  }
-
-  // These fds will only be read from or written to, but open them with
-  // read and write access to keep them open in case the subprocesses exit
-  SharedFD console_forwarder_in_wr =
-      SharedFD::Open(console_in_pipe_name.c_str(), O_RDWR);
-  if (!console_forwarder_in_wr->IsOpen()) {
-    LOG(ERROR) << "Failed to open console_forwarder input fifo for writes: "
-               << console_forwarder_in_wr->StrError();
-    return {};
-  }
-
-  SharedFD console_forwarder_out_rd =
-      SharedFD::Open(console_out_pipe_name.c_str(), O_RDWR);
-  if (!console_forwarder_out_rd->IsOpen()) {
-    LOG(ERROR) << "Failed to open console_forwarder output fifo for reads: "
-               << console_forwarder_out_rd->StrError();
-    return {};
-  }
-
-  console_forwarder_cmd.AddParameter("--console_in_fd=", console_forwarder_in_wr);
-  console_forwarder_cmd.AddParameter("--console_out_fd=", console_forwarder_out_rd);
-  return single_element_emplace(std::move(console_forwarder_cmd));
-}
-
-} // namespace cuttlefish
+}  // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch.h b/host/commands/run_cvd/launch.h
index 30d5aff..ead43bc 100644
--- a/host/commands/run_cvd/launch.h
+++ b/host/commands/run_cvd/launch.h
@@ -15,51 +15,34 @@
 
 #pragma once
 
+#include <fruit/fruit.h>
+
 #include <string>
 #include <vector>
 
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/subprocess.h"
+#include "host/libs/config/command_source.h"
+#include "host/libs/config/custom_actions.h"
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
+#include "host/libs/config/kernel_log_pipe_provider.h"
+#include "host/libs/vm_manager/vm_manager.h"
 
 namespace cuttlefish {
 
-struct KernelLogMonitorData {
-  std::vector<SharedFD> pipes;
-  std::vector<Command> commands;
-};
+fruit::Component<fruit::Required<const CuttlefishConfig, vm_manager::VmManager,
+                                 const CuttlefishConfig::InstanceSpecific>,
+                 KernelLogPipeProvider>
+launchComponent();
 
-KernelLogMonitorData LaunchKernelLogMonitor(const CuttlefishConfig& config,
-                                            unsigned int number_of_event_pipes);
-std::vector<Command> LaunchAdbConnectorIfEnabled(
-    const CuttlefishConfig& config);
-std::vector<Command> LaunchSocketVsockProxyIfEnabled(
-    const CuttlefishConfig& config, SharedFD adbd_events_pipe);
-std::vector<Command> LaunchModemSimulatorIfEnabled(
-    const CuttlefishConfig& config);
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>>
+launchModemComponent();
 
-std::vector<Command> LaunchVNCServer(const CuttlefishConfig& config);
-
-std::vector<Command> LaunchTombstoneReceiver(const CuttlefishConfig& config);
-std::vector<Command> LaunchRootCanal(const CuttlefishConfig& config);
-std::vector<Command> LaunchLogcatReceiver(const CuttlefishConfig& config);
-std::vector<Command> LaunchConfigServer(const CuttlefishConfig& config);
-
-std::vector<Command> LaunchWebRTC(const CuttlefishConfig& config,
-                                  SharedFD kernel_log_events_pipe);
-
-std::vector<Command> LaunchMetrics();
-
-std::vector<Command> LaunchGnssGrpcProxyServerIfEnabled(
-    const CuttlefishConfig& config);
-
-std::vector<Command> LaunchSecureEnvironment(const CuttlefishConfig& config);
-
-std::vector<Command> LaunchBluetoothConnector(const CuttlefishConfig& config);
-std::vector<Command> LaunchVehicleHalServerIfEnabled(
-    const CuttlefishConfig& config);
-
-std::vector<Command> LaunchConsoleForwarderIfEnabled(
-    const CuttlefishConfig& config);
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider,
+                                 const CuttlefishConfig::InstanceSpecific,
+                                 const CustomActionConfigProvider>>
+launchStreamerComponent();
 
 } // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch_adb.cpp b/host/commands/run_cvd/launch_adb.cpp
deleted file mode 100644
index b7996ee..0000000
--- a/host/commands/run_cvd/launch_adb.cpp
+++ /dev/null
@@ -1,156 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "host/commands/run_cvd/launch.h"
-
-#include <android-base/logging.h>
-#include <set>
-#include <string>
-#include <utility>
-
-#include "common/libs/fs/shared_fd.h"
-#include "common/libs/utils/subprocess.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/config/known_paths.h"
-
-namespace cuttlefish {
-
-namespace {
-
-std::string GetAdbConnectorTcpArg(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  return std::string{"0.0.0.0:"} + std::to_string(instance.host_port());
-}
-
-std::string GetAdbConnectorVsockArg(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  return std::string{"vsock:"} + std::to_string(instance.vsock_guest_cid()) +
-         std::string{":5555"};
-}
-
-bool AdbModeEnabled(const CuttlefishConfig& config, AdbMode mode) {
-  return config.adb_mode().count(mode) > 0;
-}
-
-bool AdbVsockTunnelEnabled(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  return instance.vsock_guest_cid() > 2 &&
-         AdbModeEnabled(config, AdbMode::VsockTunnel);
-}
-
-bool AdbVsockHalfTunnelEnabled(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  return instance.vsock_guest_cid() > 2 &&
-         AdbModeEnabled(config, AdbMode::VsockHalfTunnel);
-}
-
-bool AdbTcpConnectorEnabled(const CuttlefishConfig& config) {
-  bool vsock_tunnel = AdbVsockTunnelEnabled(config);
-  bool vsock_half_tunnel = AdbVsockHalfTunnelEnabled(config);
-  return config.run_adb_connector() && (vsock_tunnel || vsock_half_tunnel);
-}
-
-bool AdbVsockConnectorEnabled(const CuttlefishConfig& config) {
-  return config.run_adb_connector() &&
-         AdbModeEnabled(config, AdbMode::NativeVsock);
-}
-
-}  // namespace
-
-std::vector<Command> LaunchAdbConnectorIfEnabled(
-    const CuttlefishConfig& config) {
-  Command adb_connector(AdbConnectorBinary());
-  std::set<std::string> addresses;
-
-  if (AdbTcpConnectorEnabled(config)) {
-    addresses.insert(GetAdbConnectorTcpArg(config));
-  }
-  if (AdbVsockConnectorEnabled(config)) {
-    addresses.insert(GetAdbConnectorVsockArg(config));
-  }
-
-  if (addresses.size() == 0) {
-    return {};
-  }
-  std::string address_arg = "--addresses=";
-  for (auto& arg : addresses) {
-    address_arg += arg + ",";
-  }
-  address_arg.pop_back();
-  adb_connector.AddParameter(address_arg);
-  std::vector<Command> commands;
-  commands.emplace_back(std::move(adb_connector));
-  return std::move(commands);
-}
-
-std::vector<Command> LaunchSocketVsockProxyIfEnabled(
-    const CuttlefishConfig& config, SharedFD adbd_events_pipe) {
-  auto instance = config.ForDefaultInstance();
-  auto append = [](const std::string& s, const int i) -> std::string {
-    return s + std::to_string(i);
-  };
-  auto tcp_server =
-      SharedFD::SocketLocalServer(instance.host_port(), SOCK_STREAM);
-  CHECK(tcp_server->IsOpen())
-      << "Unable to create socket_vsock_proxy server socket: "
-      << tcp_server->StrError();
-  std::vector<Command> commands;
-  if (AdbVsockTunnelEnabled(config)) {
-    Command adb_tunnel(SocketVsockProxyBinary());
-    adb_tunnel.AddParameter("-adbd_events_fd=", adbd_events_pipe);
-    /**
-     * This socket_vsock_proxy (a.k.a. sv proxy) runs on the host. It assumes
-     * that another sv proxy runs inside the guest. see:
-     * shared/config/init.vendor.rc The sv proxy in the guest exposes
-     * vsock:cid:6520 across the cuttlefish instances in multi-tenancy. cid is
-     * different per instance.
-     *
-     * This host sv proxy should cooperate with the guest sv proxy. Thus, one
-     * end of the tunnel is vsock:cid:6520 regardless of instance number.
-     * Another end faces the host adb daemon via tcp. Thus, the server type is
-     * tcp here. The tcp port differs from instance to instance, and is
-     * instance.host_port()
-     *
-     */
-    adb_tunnel.AddParameter("--server=tcp");
-    adb_tunnel.AddParameter("--vsock_port=6520");
-    adb_tunnel.AddParameter(std::string{"--server_fd="}, tcp_server);
-    adb_tunnel.AddParameter(std::string{"--vsock_cid="} +
-                            std::to_string(instance.vsock_guest_cid()));
-    commands.emplace_back(std::move(adb_tunnel));
-  }
-  if (AdbVsockHalfTunnelEnabled(config)) {
-    Command adb_tunnel(SocketVsockProxyBinary());
-    adb_tunnel.AddParameter("-adbd_events_fd=", adbd_events_pipe);
-    /*
-     * This socket_vsock_proxy (a.k.a. sv proxy) runs on the host, and
-     * cooperates with the adbd inside the guest. See this file:
-     *  shared/device.mk, especially the line says "persist.adb.tcp.port="
-     *
-     * The guest adbd is listening on vsock:cid:5555 across cuttlefish
-     * instances. Sv proxy faces the host adb daemon via tcp. The server type
-     * should be therefore tcp, and the port should differ from instance to
-     * instance and be equal to instance.host_port()
-     */
-    adb_tunnel.AddParameter("--server=tcp");
-    adb_tunnel.AddParameter(append("--vsock_port=", 5555));
-    adb_tunnel.AddParameter(std::string{"--server_fd="}, tcp_server);
-    adb_tunnel.AddParameter(append("--vsock_cid=", instance.vsock_guest_cid()));
-    commands.emplace_back(std::move(adb_tunnel));
-  }
-  return commands;
-}
-
-}  // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch_modem.cpp b/host/commands/run_cvd/launch_modem.cpp
index 07af9f1..8648b89 100644
--- a/host/commands/run_cvd/launch_modem.cpp
+++ b/host/commands/run_cvd/launch_modem.cpp
@@ -19,25 +19,21 @@
 #include <string.h>
 #include <sstream>
 #include <string>
+#include <unordered_set>
 #include <utility>
 
 #include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/result.h"
 #include "common/libs/utils/subprocess.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/known_paths.h"
 
 namespace cuttlefish {
 
-static bool StopModemSimulator() {
-  auto config = CuttlefishConfig::Get();
-  auto instance = config->ForDefaultInstance();
-
-  std::string monitor_socket_name = "modem_simulator";
-  std::stringstream ss;
-  ss << instance.host_port();
-  monitor_socket_name.append(ss.str());
-  auto monitor_sock = SharedFD::SocketLocalClient(monitor_socket_name.c_str(),
-                                                  true, SOCK_STREAM);
+static bool StopModemSimulator(int id) {
+  std::string socket_name = "modem_simulator" + std::to_string(id);
+  auto monitor_sock =
+      SharedFD::SocketLocalClient(socket_name, true, SOCK_STREAM);
   if (!monitor_sock->IsOpen()) {
     LOG(ERROR) << "The connection to modem simulator is closed";
     return false;
@@ -63,55 +59,83 @@
   return true;
 }
 
-std::vector<Command> LaunchModemSimulatorIfEnabled(
-    const CuttlefishConfig& config) {
-  if (!config.enable_modem_simulator()) {
-    LOG(DEBUG) << "Modem simulator not enabled";
+class ModemSimulator : public CommandSource {
+ public:
+  INJECT(ModemSimulator(const CuttlefishConfig& config,
+                        const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command cmd(ModemSimulatorBinary(), [this](Subprocess* proc) {
+      auto stopped = StopModemSimulator(instance_.modem_simulator_host_id());
+      if (stopped) {
+        return StopperResult::kStopSuccess;
+      }
+      LOG(WARNING) << "Failed to stop modem simulator nicely, "
+                   << "attempting to KILL";
+      return KillSubprocess(proc) == StopperResult::kStopSuccess
+                 ? StopperResult::kStopCrash
+                 : StopperResult::kStopFailure;
+    });
+
+    auto sim_type = config_.modem_simulator_sim_type();
+    cmd.AddParameter(std::string{"-sim_type="} + std::to_string(sim_type));
+    cmd.AddParameter("-server_fds=");
+    bool first_socket = true;
+    for (const auto& socket : sockets_) {
+      if (!first_socket) {
+        cmd.AppendToLastParameter(",");
+      }
+      cmd.AppendToLastParameter(socket);
+      first_socket = false;
+    }
+
+    std::vector<Command> commands;
+    commands.emplace_back(std::move(cmd));
+    return commands;
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "ModemSimulator"; }
+  bool Enabled() const override {
+    if (!config_.enable_modem_simulator()) {
+      LOG(DEBUG) << "Modem simulator not enabled";
+    }
+    return config_.enable_modem_simulator();
+  }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    int instance_number = config_.modem_simulator_instance_number();
+    CF_EXPECT(instance_number >= 0 && instance_number < 4,
+              "Modem simulator instance number should range between 0 and 3");
+    auto ports = instance_.modem_simulator_ports();
+    for (int i = 0; i < instance_number; ++i) {
+      auto pos = ports.find(',');
+      auto temp = (pos != std::string::npos) ? ports.substr(0, pos) : ports;
+      auto port = std::stoi(temp);
+      ports = ports.substr(pos + 1);
+
+      auto modem_sim_socket = SharedFD::VsockServer(port, SOCK_STREAM);
+      CF_EXPECT(modem_sim_socket->IsOpen(), modem_sim_socket->StrError());
+      sockets_.emplace_back(std::move(modem_sim_socket));
+    }
     return {};
   }
 
-  int instance_number = config.modem_simulator_instance_number();
-  if (instance_number > 3 /* max value */ || instance_number < 0) {
-    LOG(ERROR)
-        << "Modem simulator instance number should range between 1 and 3";
-    return {};
-  }
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  std::vector<SharedFD> sockets_;
+};
 
-  Command cmd(ModemSimulatorBinary(), [](Subprocess* proc) {
-    auto stopped = StopModemSimulator();
-    if (stopped) {
-      return true;
-    }
-    LOG(WARNING) << "Failed to stop modem simulator nicely, "
-                 << "attempting to KILL";
-    return KillSubprocess(proc);
-  });
-
-  auto sim_type = config.modem_simulator_sim_type();
-  cmd.AddParameter(std::string{"-sim_type="} + std::to_string(sim_type));
-
-  auto instance = config.ForDefaultInstance();
-  auto ports = instance.modem_simulator_ports();
-  cmd.AddParameter("-server_fds=");
-  for (int i = 0; i < instance_number; ++i) {
-    auto pos = ports.find(',');
-    auto temp = (pos != std::string::npos) ? ports.substr(0, pos) : ports;
-    auto port = std::stoi(temp);
-    ports = ports.substr(pos + 1);
-
-    auto socket = SharedFD::VsockServer(port, SOCK_STREAM);
-    CHECK(socket->IsOpen())
-        << "Unable to create modem simulator server socket: "
-        << socket->StrError();
-    if (i > 0) {
-      cmd.AppendToLastParameter(",");
-    }
-    cmd.AppendToLastParameter(socket);
-  }
-
-  std::vector<Command> commands;
-  commands.emplace_back(std::move(cmd));
-  return commands;
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>>
+launchModemComponent() {
+  return fruit::createComponent()
+      .addMultibinding<CommandSource, ModemSimulator>()
+      .addMultibinding<SetupFeature, ModemSimulator>();
 }
 
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch_streamer.cpp b/host/commands/run_cvd/launch_streamer.cpp
index 7ae3f1b..ef9b0ca 100644
--- a/host/commands/run_cvd/launch_streamer.cpp
+++ b/host/commands/run_cvd/launch_streamer.cpp
@@ -16,12 +16,15 @@
 #include "host/commands/run_cvd/launch.h"
 
 #include <android-base/logging.h>
+#include <sstream>
 #include <string>
 #include <utility>
 
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
+#include "host/commands/run_cvd/reporting.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/known_paths.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
@@ -41,85 +44,12 @@
   return server;
 }
 
-// Creates the frame and input sockets and add the relevant arguments to the vnc
-// server and webrtc commands
-void CreateStreamerServers(Command* cmd, const CuttlefishConfig& config) {
-  std::vector<SharedFD> touch_servers;
-  SharedFD keyboard_server;
-
-  auto instance = config.ForDefaultInstance();
-  auto use_vsockets = config.vm_manager() == vm_manager::QemuManager::name();
-  for (int i = 0; i < config.display_configs().size(); ++i) {
-    touch_servers.push_back(
-        use_vsockets
-            ? SharedFD::VsockServer(instance.touch_server_port(), SOCK_STREAM)
-            : CreateUnixInputServer(instance.touch_socket_path(i)));
-    if (!touch_servers.back()->IsOpen()) {
-      LOG(ERROR) << "Could not open touch server: "
-                 << touch_servers.back()->StrError();
-      return;
-    }
-  }
-  if (!touch_servers.empty()) {
-    cmd->AddParameter("-touch_fds=", touch_servers[0]);
-    for (int i = 1; i < touch_servers.size(); ++i) {
-      cmd->AppendToLastParameter(",", touch_servers[i]);
-    }
-  }
-
-  if (use_vsockets) {
-    cmd->AddParameter("-write_virtio_input");
-
-    keyboard_server =
-        SharedFD::VsockServer(instance.keyboard_server_port(), SOCK_STREAM);
-  } else {
-    keyboard_server = CreateUnixInputServer(instance.keyboard_socket_path());
-  }
-
-  if (!keyboard_server->IsOpen()) {
-    LOG(ERROR) << "Could not open keyboard server: "
-               << keyboard_server->StrError();
-    return;
-  }
-  cmd->AddParameter("-keyboard_fd=", keyboard_server);
-
-  if (config.enable_webrtc() &&
-      config.vm_manager() == vm_manager::CrosvmManager::name()) {
-    SharedFD switches_server =
-        CreateUnixInputServer(instance.switches_socket_path());
-    if (!switches_server->IsOpen()) {
-      LOG(ERROR) << "Could not open switches server: "
-                 << switches_server->StrError();
-      return;
-    }
-    cmd->AddParameter("-switches_fd=", switches_server);
-  }
-
-  SharedFD frames_server = CreateUnixInputServer(instance.frames_socket_path());
-  if (!frames_server->IsOpen()) {
-    LOG(ERROR) << "Could not open frames server: " << frames_server->StrError();
-    return;
-  }
-  cmd->AddParameter("-frame_server_fd=", frames_server);
-
-  if (config.enable_audio()) {
-    auto path = config.ForDefaultInstance().audio_server_path();
-    auto audio_server =
-        SharedFD::SocketLocalServer(path.c_str(), false, SOCK_SEQPACKET, 0666);
-    if (!audio_server->IsOpen()) {
-      LOG(ERROR) << "Could not create audio server: "
-                 << audio_server->StrError();
-      return;
-    }
-    cmd->AddParameter("--audio_server_fd=", audio_server);
-  }
-}
-
-std::vector<Command> LaunchCustomActionServers(Command& webrtc_cmd,
-                                               const CuttlefishConfig& config) {
+std::vector<Command> LaunchCustomActionServers(
+    Command& webrtc_cmd,
+    const std::vector<CustomActionConfig>& custom_actions) {
   bool first = true;
   std::vector<Command> commands;
-  for (const auto& custom_action : config.custom_actions()) {
+  for (const auto& custom_action : custom_actions) {
     if (custom_action.server) {
       // Create a socket pair that will be used for communication between
       // WebRTC and the action server.
@@ -133,8 +63,8 @@
 
       // Launch the action server, providing its socket pair fd as the only
       // argument.
-      std::string binary = "bin/" + *(custom_action.server);
-      Command command(DefaultHostArtifactsPath(binary));
+      auto binary = HostBinaryPath(*(custom_action.server));
+      Command command(binary);
       command.AddParameter(action_server_socket);
       commands.emplace_back(std::move(command));
 
@@ -152,84 +82,224 @@
   return commands;
 }
 
+// Creates the frame and input sockets and add the relevant arguments to
+// webrtc commands
+class StreamerSockets : public virtual SetupFeature {
+ public:
+  INJECT(StreamerSockets(const CuttlefishConfig& config,
+                         const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
+
+  void AppendCommandArguments(Command& cmd) {
+    if (config_.vm_manager() == vm_manager::QemuManager::name()) {
+      cmd.AddParameter("-write_virtio_input");
+    }
+    if (!touch_servers_.empty()) {
+      cmd.AddParameter("-touch_fds=", touch_servers_[0]);
+      for (int i = 1; i < touch_servers_.size(); ++i) {
+        cmd.AppendToLastParameter(",", touch_servers_[i]);
+      }
+    }
+    cmd.AddParameter("-keyboard_fd=", keyboard_server_);
+    cmd.AddParameter("-frame_server_fd=", frames_server_);
+    if (config_.enable_audio()) {
+      cmd.AddParameter("--audio_server_fd=", audio_server_);
+    }
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "StreamerSockets"; }
+  bool Enabled() const override {
+    bool is_qemu = config_.vm_manager() == vm_manager::QemuManager::name();
+    bool is_accelerated = config_.gpu_mode() != kGpuModeGuestSwiftshader;
+    return !(is_qemu && is_accelerated);
+  }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+
+  Result<void> ResultSetup() override {
+    auto use_vsockets = config_.vm_manager() == vm_manager::QemuManager::name();
+    for (int i = 0; i < config_.display_configs().size(); ++i) {
+      SharedFD touch_socket =
+          use_vsockets ? SharedFD::VsockServer(instance_.touch_server_port(),
+                                               SOCK_STREAM)
+                       : CreateUnixInputServer(instance_.touch_socket_path(i));
+      CF_EXPECT(touch_socket->IsOpen(), touch_socket->StrError());
+      touch_servers_.emplace_back(std::move(touch_socket));
+    }
+    keyboard_server_ =
+        use_vsockets ? SharedFD::VsockServer(instance_.keyboard_server_port(),
+                                             SOCK_STREAM)
+                     : CreateUnixInputServer(instance_.keyboard_socket_path());
+    CF_EXPECT(keyboard_server_->IsOpen(), keyboard_server_->StrError());
+
+    frames_server_ = CreateUnixInputServer(instance_.frames_socket_path());
+    CF_EXPECT(frames_server_->IsOpen(), frames_server_->StrError());
+    // TODO(schuffelen): Make this a separate optional feature?
+    if (config_.enable_audio()) {
+      auto path = config_.ForDefaultInstance().audio_server_path();
+      audio_server_ =
+          SharedFD::SocketLocalServer(path, false, SOCK_SEQPACKET, 0666);
+      CF_EXPECT(audio_server_->IsOpen(), audio_server_->StrError());
+    }
+    return {};
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  std::vector<SharedFD> touch_servers_;
+  SharedFD keyboard_server_;
+  SharedFD frames_server_;
+  SharedFD audio_server_;
+};
+
+class WebRtcServer : public virtual CommandSource,
+                     public DiagnosticInformation {
+ public:
+  INJECT(WebRtcServer(const CuttlefishConfig& config,
+                      const CuttlefishConfig::InstanceSpecific& instance,
+                      StreamerSockets& sockets,
+                      KernelLogPipeProvider& log_pipe_provider,
+                      const CustomActionConfigProvider& custom_action_config))
+      : config_(config),
+        instance_(instance),
+        sockets_(sockets),
+        log_pipe_provider_(log_pipe_provider),
+        custom_action_config_(custom_action_config) {}
+  // DiagnosticInformation
+  std::vector<std::string> Diagnostics() const override {
+    if (!Enabled() || !config_.ForDefaultInstance().start_webrtc_sig_server()) {
+      // When WebRTC is enabled but an operator other than the one launched by
+      // run_cvd is used there is no way to know the url to which to point the
+      // browser to.
+      return {};
+    }
+    std::ostringstream out;
+    out << "Point your browser to https://" << config_.sig_server_address()
+        << ":" << config_.sig_server_port() << " to interact with the device.";
+    return {out.str()};
+  }
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    std::vector<Command> commands;
+    if (instance_.start_webrtc_sig_server()) {
+      Command sig_server(WebRtcSigServerBinary());
+      sig_server.AddParameter("-assets_dir=", config_.webrtc_assets_dir());
+      sig_server.AddParameter(
+          "-use_secure_http=",
+          config_.sig_server_secure() ? "true" : "false");
+      if (!config_.webrtc_certs_dir().empty()) {
+        sig_server.AddParameter("-certs_dir=", config_.webrtc_certs_dir());
+      }
+      sig_server.AddParameter("-http_server_port=", config_.sig_server_port());
+      commands.emplace_back(std::move(sig_server));
+    }
+
+    if (instance_.start_webrtc_sig_server_proxy()) {
+      Command sig_proxy(WebRtcSigServerProxyBinary());
+      sig_proxy.AddParameter("-server_port=", config_.sig_server_port());
+      commands.emplace_back(std::move(sig_proxy));
+    }
+
+    auto stopper = [host_socket = std::move(host_socket_)](Subprocess* proc) {
+      struct timeval timeout;
+      timeout.tv_sec = 3;
+      timeout.tv_usec = 0;
+      CHECK(host_socket->SetSockOpt(SOL_SOCKET, SO_RCVTIMEO, &timeout,
+                                    sizeof(timeout)) == 0)
+          << "Could not set receive timeout";
+
+      WriteAll(host_socket, "C");
+      char response[1];
+      int read_ret = host_socket->Read(response, sizeof(response));
+      if (read_ret != 0) {
+        LOG(ERROR) << "Failed to read response from webrtc";
+        return KillSubprocess(proc);
+      }
+      return KillSubprocess(proc) == StopperResult::kStopSuccess
+                 ? StopperResult::kStopCrash
+                 : StopperResult::kStopFailure;
+    };
+
+    Command webrtc(WebRtcBinary(), stopper);
+    webrtc.UnsetFromEnvironment("http_proxy");
+    sockets_.AppendCommandArguments(webrtc);
+    if (config_.vm_manager() == vm_manager::CrosvmManager::name()) {
+      webrtc.AddParameter("-switches_fd=", switches_server_);
+    }
+    // Currently there is no way to ensure the signaling server will already
+    // have bound the socket to the port by the time the webrtc process runs
+    // (the common technique of doing it from the launcher is not possible here
+    // as the server library being used creates its own sockets). However, this
+    // issue is mitigated slightly by doing some retrying and backoff in the
+    // webrtc process when connecting to the websocket, so it shouldn't be an
+    // issue most of the time.
+    webrtc.AddParameter("--command_fd=", client_socket_);
+    webrtc.AddParameter("-kernel_log_events_fd=", kernel_log_events_pipe_);
+    webrtc.AddParameter("-client_dir=",
+                        DefaultHostArtifactsPath("usr/share/webrtc/assets"));
+
+    // TODO get from launcher params
+    const auto& actions = custom_action_config_.CustomActions();
+    for (auto& action : LaunchCustomActionServers(webrtc, actions)) {
+      commands.emplace_back(std::move(action));
+    }
+    commands.emplace_back(std::move(webrtc));
+
+    return commands;
+  }
+
+  // SetupFeature
+  bool Enabled() const override {
+    return sockets_.Enabled() && config_.enable_webrtc();
+  }
+
+ private:
+  std::string Name() const override { return "WebRtcServer"; }
+  std::unordered_set<SetupFeature*> Dependencies() const override {
+    return {static_cast<SetupFeature*>(&sockets_),
+            static_cast<SetupFeature*>(&log_pipe_provider_)};
+  }
+
+  Result<void> ResultSetup() override {
+    CF_EXPECT(SharedFD::SocketPair(AF_LOCAL, SOCK_STREAM, 0, &client_socket_,
+                                   &host_socket_),
+              client_socket_->StrError());
+    if (config_.vm_manager() == vm_manager::CrosvmManager::name()) {
+      switches_server_ =
+          CreateUnixInputServer(instance_.switches_socket_path());
+      CF_EXPECT(switches_server_->IsOpen(), switches_server_->StrError());
+    }
+    kernel_log_events_pipe_ = log_pipe_provider_.KernelLogPipe();
+    CF_EXPECT(kernel_log_events_pipe_->IsOpen(),
+              kernel_log_events_pipe_->StrError());
+    return {};
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  StreamerSockets& sockets_;
+  KernelLogPipeProvider& log_pipe_provider_;
+  const CustomActionConfigProvider& custom_action_config_;
+  SharedFD kernel_log_events_pipe_;
+  SharedFD client_socket_;
+  SharedFD host_socket_;
+  SharedFD switches_server_;
+};
+
 }  // namespace
 
-std::vector<Command> LaunchVNCServer(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  // Launch the vnc server, don't wait for it to complete
-  auto port_options = "-port=" + std::to_string(instance.vnc_server_port());
-  Command vnc_server(VncServerBinary());
-  vnc_server.AddParameter(port_options);
-
-  CreateStreamerServers(&vnc_server, config);
-
-  std::vector<Command> commands;
-  commands.emplace_back(std::move(vnc_server));
-  return std::move(commands);
-}
-
-std::vector<Command> LaunchWebRTC(const CuttlefishConfig& config,
-                                  SharedFD kernel_log_events_pipe) {
-  std::vector<Command> commands;
-  if (config.ForDefaultInstance().start_webrtc_sig_server()) {
-    Command sig_server(WebRtcSigServerBinary());
-    sig_server.AddParameter("-assets_dir=", config.webrtc_assets_dir());
-    if (!config.webrtc_certs_dir().empty()) {
-      sig_server.AddParameter("-certs_dir=", config.webrtc_certs_dir());
-    }
-    sig_server.AddParameter("-http_server_port=", config.sig_server_port());
-    commands.emplace_back(std::move(sig_server));
-  }
-
-  // Currently there is no way to ensure the signaling server will already have
-  // bound the socket to the port by the time the webrtc process runs (the
-  // common technique of doing it from the launcher is not possible here as the
-  // server library being used creates its own sockets). However, this issue is
-  // mitigated slightly by doing some retrying and backoff in the webrtc process
-  // when connecting to the websocket, so it shouldn't be an issue most of the
-  // time.
-  SharedFD client_socket;
-  SharedFD host_socket;
-  CHECK(SharedFD::SocketPair(AF_LOCAL, SOCK_STREAM, 0, &client_socket,
-                             &host_socket))
-      << "Could not open command socket for webRTC";
-
-  auto stopper = [host_socket = std::move(host_socket)](Subprocess* proc) {
-    struct timeval timeout;
-    timeout.tv_sec = 3;
-    timeout.tv_usec = 0;
-    CHECK(host_socket->SetSockOpt(SOL_SOCKET, SO_RCVTIMEO, &timeout,
-                                  sizeof(timeout)) == 0)
-        << "Could not set receive timeout";
-
-    WriteAll(host_socket, "C");
-    char response[1];
-    int read_ret = host_socket->Read(response, sizeof(response));
-    if (read_ret != 0) {
-      LOG(ERROR) << "Failed to read response from webrtc";
-    }
-    cuttlefish::KillSubprocess(proc);
-    return true;
-  };
-
-  Command webrtc(WebRtcBinary(), SubprocessStopper(stopper));
-
-  webrtc.UnsetFromEnvironment({"http_proxy"});
-
-  CreateStreamerServers(&webrtc, config);
-
-  webrtc.AddParameter("--command_fd=", client_socket);
-  webrtc.AddParameter("-kernel_log_events_fd=", kernel_log_events_pipe);
-
-  auto actions = LaunchCustomActionServers(webrtc, config);
-
-  // TODO get from launcher params
-  commands.emplace_back(std::move(webrtc));
-  for (auto& action : actions) {
-    commands.emplace_back(std::move(action));
-  }
-
-  return commands;
+fruit::Component<fruit::Required<const CuttlefishConfig, KernelLogPipeProvider,
+                                 const CuttlefishConfig::InstanceSpecific,
+                                 const CustomActionConfigProvider>>
+launchStreamerComponent() {
+  return fruit::createComponent()
+      .addMultibinding<CommandSource, WebRtcServer>()
+      .addMultibinding<DiagnosticInformation, WebRtcServer>()
+      .addMultibinding<SetupFeature, StreamerSockets>()
+      .addMultibinding<SetupFeature, WebRtcServer>();
 }
 
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/main.cc b/host/commands/run_cvd/main.cc
index 944f76c..9332ccc 100644
--- a/host/commands/run_cvd/main.cc
+++ b/host/commands/run_cvd/main.cc
@@ -14,375 +14,223 @@
  * limitations under the License.
  */
 
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <fruit/fruit.h>
+#include <gflags/gflags.h>
 #include <unistd.h>
+
 #include <fstream>
-#include <iostream>
 #include <memory>
 #include <string>
 #include <utility>
 #include <vector>
 
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <gflags/gflags.h>
-
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
-#include "common/libs/utils/network.h"
 #include "common/libs/utils/size_utils.h"
 #include "common/libs/utils/subprocess.h"
 #include "common/libs/utils/tee_logging.h"
 #include "host/commands/run_cvd/boot_state_machine.h"
 #include "host/commands/run_cvd/launch.h"
 #include "host/commands/run_cvd/process_monitor.h"
+#include "host/commands/run_cvd/reporting.h"
 #include "host/commands/run_cvd/runner_defs.h"
 #include "host/commands/run_cvd/server_loop.h"
+#include "host/commands/run_cvd/validate.h"
+#include "host/libs/config/adb/adb.h"
+#include "host/libs/config/config_flag.h"
+#include "host/libs/config/config_fragment.h"
+#include "host/libs/config/custom_actions.h"
 #include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/vm_manager/host_configuration.h"
 #include "host/libs/vm_manager/vm_manager.h"
 
-DEFINE_int32(reboot_notification_fd, -1,
-             "A file descriptor to notify when boot completes.");
-
 namespace cuttlefish {
 
-using vm_manager::GetVmManager;
-using vm_manager::ValidateHostConfiguration;
-
 namespace {
 
-constexpr char kGreenColor[] = "\033[1;32m";
-constexpr char kResetColor[] = "\033[0m";
+class CuttlefishEnvironment : public SetupFeature,
+                              public DiagnosticInformation {
+ public:
+  INJECT(
+      CuttlefishEnvironment(const CuttlefishConfig& config,
+                            const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
 
-bool WriteCuttlefishEnvironment(const CuttlefishConfig& config) {
-  auto env = SharedFD::Open(config.cuttlefish_env_path().c_str(),
-                            O_CREAT | O_RDWR, 0755);
-  if (!env->IsOpen()) {
-    LOG(ERROR) << "Unable to create cuttlefish.env file";
-    return false;
+  // DiagnosticInformation
+  std::vector<std::string> Diagnostics() const override {
+    auto config_path = instance_.PerInstancePath("cuttlefish_config.json");
+    return {
+        "Launcher log: " + instance_.launcher_log_path(),
+        "Instance configuration: " + config_path,
+        "Instance environment: " + config_.cuttlefish_env_path(),
+    };
   }
-  auto instance = config.ForDefaultInstance();
-  std::string config_env = "export CUTTLEFISH_PER_INSTANCE_PATH=\"" +
-                           instance.PerInstancePath(".") + "\"\n";
-  config_env += "export ANDROID_SERIAL=" + instance.adb_ip_and_port() + "\n";
-  env->Write(config_env.c_str(), config_env.size());
-  return true;
+
+  // SetupFeature
+  std::string Name() const override { return "CuttlefishEnvironment"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override {
+    auto env =
+        SharedFD::Open(config_.cuttlefish_env_path(), O_CREAT | O_RDWR, 0755);
+    if (!env->IsOpen()) {
+      LOG(ERROR) << "Unable to create cuttlefish.env file";
+      return false;
+    }
+    std::string config_env = "export CUTTLEFISH_PER_INSTANCE_PATH=\"" +
+                             instance_.PerInstancePath(".") + "\"\n";
+    config_env += "export ANDROID_SERIAL=" + instance_.adb_ip_and_port() + "\n";
+    auto written = WriteAll(env, config_env);
+    if (written != config_env.size()) {
+      LOG(ERROR) << "Failed to write all of \"" << config_env << "\", "
+                 << "only wrote " << written << " bytes. Error was "
+                 << env->StrError();
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+fruit::Component<ServerLoop> runCvdComponent(
+    const CuttlefishConfig* config,
+    const CuttlefishConfig::InstanceSpecific* instance) {
+  return fruit::createComponent()
+      .addMultibinding<DiagnosticInformation, CuttlefishEnvironment>()
+      .addMultibinding<SetupFeature, CuttlefishEnvironment>()
+      .bindInstance(*config)
+      .bindInstance(*instance)
+      .install(AdbConfigComponent)
+      .install(AdbConfigFragmentComponent)
+      .install(bootStateMachineComponent)
+      .install(ConfigFlagPlaceholder)
+      .install(CustomActionsComponent)
+      .install(LaunchAdbComponent)
+      .install(launchComponent)
+      .install(launchModemComponent)
+      .install(launchStreamerComponent)
+      .install(serverLoopComponent)
+      .install(validationComponent)
+      .install(vm_manager::VmManagerComponent);
 }
 
-// Forks and returns the write end of a pipe to the child process. The parent
-// process waits for boot events to come through the pipe and exits accordingly.
-SharedFD DaemonizeLauncher(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  SharedFD read_end, write_end;
-  if (!SharedFD::Pipe(&read_end, &write_end)) {
-    LOG(ERROR) << "Unable to create pipe";
-    return {}; // a closed FD
-  }
-  auto pid = fork();
-  if (pid) {
-    // Explicitly close here, otherwise we may end up reading forever if the
-    // child process dies.
-    write_end->Close();
-    RunnerExitCodes exit_code;
-    auto bytes_read = read_end->Read(&exit_code, sizeof(exit_code));
-    if (bytes_read != sizeof(exit_code)) {
-      LOG(ERROR) << "Failed to read a complete exit code, read " << bytes_read
-                 << " bytes only instead of the expected " << sizeof(exit_code);
-      exit_code = RunnerExitCodes::kPipeIOError;
-    } else if (exit_code == RunnerExitCodes::kSuccess) {
-      LOG(INFO) << "Virtual device booted successfully";
-    } else if (exit_code == RunnerExitCodes::kVirtualDeviceBootFailed) {
-      LOG(ERROR) << "Virtual device failed to boot";
-    } else {
-      LOG(ERROR) << "Unexpected exit code: " << exit_code;
-    }
-    if (exit_code == RunnerExitCodes::kSuccess) {
-      LOG(INFO) << kBootCompletedMessage;
-    } else {
-      LOG(INFO) << kBootFailedMessage;
-    }
-    std::exit(exit_code);
-  } else {
-    // The child returns the write end of the pipe
-    if (daemon(/*nochdir*/ 1, /*noclose*/ 1) != 0) {
-      LOG(ERROR) << "Failed to daemonize child process: " << strerror(errno);
-      std::exit(RunnerExitCodes::kDaemonizationError);
-    }
-    // Redirect standard I/O
-    auto log_path = instance.launcher_log_path();
-    auto log = SharedFD::Open(log_path.c_str(), O_CREAT | O_WRONLY | O_APPEND,
-                              S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
-    if (!log->IsOpen()) {
-      LOG(ERROR) << "Failed to create launcher log file: " << log->StrError();
-      std::exit(RunnerExitCodes::kDaemonizationError);
-    }
-    ::android::base::SetLogger(
-        TeeLogger({{LogFileSeverity(), log, MetadataLevel::FULL}}));
-    auto dev_null = SharedFD::Open("/dev/null", O_RDONLY);
-    if (!dev_null->IsOpen()) {
-      LOG(ERROR) << "Failed to open /dev/null: " << dev_null->StrError();
-      std::exit(RunnerExitCodes::kDaemonizationError);
-    }
-    if (dev_null->UNMANAGED_Dup2(0) < 0) {
-      LOG(ERROR) << "Failed dup2 stdin: " << dev_null->StrError();
-      std::exit(RunnerExitCodes::kDaemonizationError);
-    }
-    if (log->UNMANAGED_Dup2(1) < 0) {
-      LOG(ERROR) << "Failed dup2 stdout: " << log->StrError();
-      std::exit(RunnerExitCodes::kDaemonizationError);
-    }
-    if (log->UNMANAGED_Dup2(2) < 0) {
-      LOG(ERROR) << "Failed dup2 seterr: " << log->StrError();
-      std::exit(RunnerExitCodes::kDaemonizationError);
-    }
-
-    read_end->Close();
-    return write_end;
-  }
+Result<void> StdinValid() {
+  CF_EXPECT(!isatty(0),
+            "stdin was a tty, expected to be passed the output of a"
+            " previous stage. Did you mean to run launch_cvd?");
+  CF_EXPECT(errno != EBADF,
+            "stdin was not a valid file descriptor, expected to be passed the "
+            "output of assemble_cvd. Did you mean to run launch_cvd?");
+  return {};
 }
 
-std::string GetConfigFilePath(const CuttlefishConfig& config) {
-  auto instance = config.ForDefaultInstance();
-  return instance.PerInstancePath("cuttlefish_config.json");
-}
-
-void PrintStreamingInformation(const CuttlefishConfig& config) {
-  if (config.ForDefaultInstance().start_webrtc_sig_server()) {
-    // TODO (jemoreira): Change this when webrtc is moved to the debian package.
-    LOG(INFO) << kGreenColor << "Point your browser to https://"
-              << config.sig_server_address() << ":" << config.sig_server_port()
-              << " to interact with the device." << kResetColor;
-  } else if (config.enable_vnc_server()) {
-    LOG(INFO) << kGreenColor << "VNC server started on port "
-              << config.ForDefaultInstance().vnc_server_port() << kResetColor;
-  }
-  // When WebRTC is enabled but an operator other than the one launched by
-  // run_cvd is used there is no way to know the url to which to point the
-  // browser to.
-}
-
-}  // namespace
-
-int RunCvdMain(int argc, char** argv) {
-  setenv("ANDROID_LOG_TAGS", "*:v", /* overwrite */ 0);
-  ::android::base::InitLogging(argv, android::base::StderrLogger);
-  google::ParseCommandLineFlags(&argc, &argv, false);
-
-  if (isatty(0)) {
-    LOG(FATAL) << "stdin was a tty, expected to be passed the output of a previous stage. "
-               << "Did you mean to run launch_cvd?";
-    return RunnerExitCodes::kInvalidHostConfiguration;
-  } else {
-    int error_num = errno;
-    if (error_num == EBADF) {
-      LOG(FATAL) << "stdin was not a valid file descriptor, expected to be passed the output "
-                 << "of assemble_cvd. Did you mean to run launch_cvd?";
-      return RunnerExitCodes::kInvalidHostConfiguration;
-    }
-  }
-
+Result<const CuttlefishConfig*> FindConfigFromStdin() {
   std::string input_files_str;
   {
     auto input_fd = SharedFD::Dup(0);
     auto bytes_read = ReadAll(input_fd, &input_files_str);
-    if (bytes_read < 0) {
-      LOG(FATAL) << "Failed to read input files. Error was \"" << input_fd->StrError() << "\"";
-    }
+    CF_EXPECT(bytes_read >= 0, "Failed to read input files. Error was \""
+                                   << input_fd->StrError() << "\"");
   }
-  std::vector<std::string> input_files = android::base::Split(input_files_str, "\n");
-  bool found_config = false;
+  std::vector<std::string> input_files =
+      android::base::Split(input_files_str, "\n");
   for (const auto& file : input_files) {
     if (file.find("cuttlefish_config.json") != std::string::npos) {
-      found_config = true;
       setenv(kCuttlefishConfigEnvVarName, file.c_str(), /* overwrite */ false);
     }
   }
-  if (!found_config) {
-    return RunnerExitCodes::kCuttlefishConfigurationInitError;
-  }
+  return CF_EXPECT(CuttlefishConfig::Get());  // Null check
+}
 
-  auto config = CuttlefishConfig::Get();
-  auto instance = config->ForDefaultInstance();
-
+void ConfigureLogs(const CuttlefishConfig& config,
+                   const CuttlefishConfig::InstanceSpecific& instance) {
   auto log_path = instance.launcher_log_path();
 
-  {
-    std::ofstream launcher_log_ofstream(log_path.c_str());
-    auto assembly_path = config->AssemblyPath("assemble_cvd.log");
-    std::ifstream assembly_log_ifstream(assembly_path);
-    if (assembly_log_ifstream) {
-      auto assemble_log = ReadFile(assembly_path);
-      launcher_log_ofstream << assemble_log;
-    }
+  std::ofstream launcher_log_ofstream(log_path.c_str());
+  auto assembly_path = config.AssemblyPath("assemble_cvd.log");
+  std::ifstream assembly_log_ifstream(assembly_path);
+  if (assembly_log_ifstream) {
+    auto assemble_log = ReadFile(assembly_path);
+    launcher_log_ofstream << assemble_log;
   }
-  ::android::base::SetLogger(LogToStderrAndFiles({log_path}));
+  std::string prefix;
+  if (config.Instances().size() > 1) {
+    prefix = instance.instance_name() + ": ";
+  }
+  ::android::base::SetLogger(LogToStderrAndFiles({log_path}, prefix));
+}
 
+Result<void> ChdirIntoRuntimeDir(
+    const CuttlefishConfig::InstanceSpecific& instance) {
   // Change working directory to the instance directory as early as possible to
   // ensure all host processes have the same working dir. This helps stop_cvd
   // find the running processes when it can't establish a communication with the
   // launcher.
-  auto chdir_ret = chdir(instance.instance_dir().c_str());
-  if (chdir_ret != 0) {
-    auto error = errno;
-    LOG(ERROR) << "Unable to change dir into instance directory ("
-               << instance.instance_dir() << "): " << strerror(error);
-    return RunnerExitCodes::kInstanceDirCreationError;
+  CF_EXPECT(chdir(instance.instance_dir().c_str()) == 0,
+            "Unable to change dir into instance directory \""
+                << instance.instance_dir() << "\": " << strerror(errno));
+  return {};
+}
+
+}  // namespace
+
+Result<void> RunCvdMain(int argc, char** argv) {
+  setenv("ANDROID_LOG_TAGS", "*:v", /* overwrite */ 0);
+  ::android::base::InitLogging(argv, android::base::StderrLogger);
+  google::ParseCommandLineFlags(&argc, &argv, false);
+
+  CF_EXPECT(StdinValid(), "Invalid stdin");
+  auto config = CF_EXPECT(FindConfigFromStdin());
+  auto instance = config->ForDefaultInstance();
+
+  ConfigureLogs(*config, instance);
+  CF_EXPECT(ChdirIntoRuntimeDir(instance));
+
+  fruit::Injector<ServerLoop> injector(runCvdComponent, config, &instance);
+
+  for (auto& fragment : injector.getMultibindings<ConfigFragment>()) {
+    CF_EXPECT(config->LoadFragment(*fragment));
   }
 
-  auto used_tap_devices = TapInterfacesInUse();
-  if (used_tap_devices.count(instance.wifi_tap_name())) {
-    LOG(ERROR) << "Wifi TAP device already in use";
-    return RunnerExitCodes::kTapDeviceInUse;
-  } else if (used_tap_devices.count(instance.mobile_tap_name())) {
-    LOG(ERROR) << "Mobile TAP device already in use";
-    return RunnerExitCodes::kTapDeviceInUse;
-  } else if (used_tap_devices.count(instance.ethernet_tap_name())) {
-    LOG(ERROR) << "Ethernet TAP device already in use";
-  }
+  // One of the setup features can consume most output, so print this early.
+  DiagnosticInformation::PrintAll(
+      injector.getMultibindings<DiagnosticInformation>());
 
-  auto vm_manager = GetVmManager(config->vm_manager(), config->target_arch());
-
-#ifndef __ANDROID__
-  // Check host configuration
-  std::vector<std::string> config_commands;
-  if (!ValidateHostConfiguration(&config_commands)) {
-    LOG(ERROR) << "Validation of user configuration failed";
-    std::cout << "Execute the following to correctly configure:" << std::endl;
-    for (auto& command : config_commands) {
-      std::cout << "  " << command << std::endl;
-    }
-    std::cout << "You may need to logout for the changes to take effect"
-              << std::endl;
-    return RunnerExitCodes::kInvalidHostConfiguration;
-  }
-#endif
-
-  if (!WriteCuttlefishEnvironment(*config)) {
-    LOG(ERROR) << "Unable to write cuttlefish environment file";
-  }
-
-  PrintStreamingInformation(*config);
-
-  if (config->console()) {
-    LOG(INFO) << kGreenColor << "To access the console run: screen "
-              << instance.console_path() << kResetColor;
-  } else {
-    LOG(INFO) << kGreenColor
-              << "Serial console is disabled; use -console=true to enable it"
-              << kResetColor;
-  }
-
-  LOG(INFO) << kGreenColor
-            << "The following files contain useful debugging information:"
-            << kResetColor;
-  LOG(INFO) << kGreenColor
-            << "  Launcher log: " << instance.launcher_log_path()
-            << kResetColor;
-  LOG(INFO) << kGreenColor
-            << "  Android's logcat output: " << instance.logcat_path()
-            << kResetColor;
-  LOG(INFO) << kGreenColor
-            << "  Kernel log: " << instance.PerInstancePath("kernel.log")
-            << kResetColor;
-  LOG(INFO) << kGreenColor
-            << "  Instance configuration: " << GetConfigFilePath(*config)
-            << kResetColor;
-  LOG(INFO) << kGreenColor
-            << "  Instance environment: " << config->cuttlefish_env_path()
-            << kResetColor;
-
-  auto launcher_monitor_path = instance.launcher_monitor_socket_path();
-  auto launcher_monitor_socket = SharedFD::SocketLocalServer(
-      launcher_monitor_path.c_str(), false, SOCK_STREAM, 0666);
-  if (!launcher_monitor_socket->IsOpen()) {
-    LOG(ERROR) << "Error when opening launcher server: "
-               << launcher_monitor_socket->StrError();
-    return RunnerExitCodes::kMonitorCreationFailed;
-  }
-  SharedFD foreground_launcher_pipe;
-  if (config->run_as_daemon()) {
-    foreground_launcher_pipe = DaemonizeLauncher(*config);
-    if (!foreground_launcher_pipe->IsOpen()) {
-      return RunnerExitCodes::kDaemonizationError;
-    }
-  } else {
-    // Make sure the launcher runs in its own process group even when running in
-    // foreground
-    if (getsid(0) != getpid()) {
-      int retval = setpgid(0, 0);
-      if (retval) {
-        LOG(ERROR) << "Failed to create new process group: " << strerror(errno);
-        std::exit(RunnerExitCodes::kProcessGroupError);
-      }
-    }
-  }
-
-  SharedFD reboot_notification;
-  if (FLAGS_reboot_notification_fd >= 0) {
-    reboot_notification = SharedFD::Dup(FLAGS_reboot_notification_fd);
-    close(FLAGS_reboot_notification_fd);
-  }
+  const auto& features = injector.getMultibindings<SetupFeature>();
+  CF_EXPECT(SetupFeature::RunSetup(features));
 
   // Monitor and restart host processes supporting the CVD
-  ProcessMonitor process_monitor(config->restart_subprocesses());
+  ProcessMonitor::Properties process_monitor_properties;
+  process_monitor_properties.RestartSubprocesses(
+      config->restart_subprocesses());
 
-  if (config->enable_metrics() == CuttlefishConfig::kYes) {
-    process_monitor.AddCommands(LaunchMetrics());
-  }
-  process_monitor.AddCommands(LaunchModemSimulatorIfEnabled(*config));
-
-  auto kernel_log_monitor = LaunchKernelLogMonitor(*config, 3);
-  SharedFD boot_events_pipe = kernel_log_monitor.pipes[0];
-  SharedFD adbd_events_pipe = kernel_log_monitor.pipes[1];
-  SharedFD webrtc_events_pipe = kernel_log_monitor.pipes[2];
-  kernel_log_monitor.pipes.clear();
-  process_monitor.AddCommands(std::move(kernel_log_monitor.commands));
-
-  CvdBootStateMachine boot_state_machine(foreground_launcher_pipe,
-                                         reboot_notification, boot_events_pipe);
-
-  process_monitor.AddCommands(LaunchRootCanal(*config));
-  process_monitor.AddCommands(LaunchLogcatReceiver(*config));
-  process_monitor.AddCommands(LaunchConfigServer(*config));
-  process_monitor.AddCommands(LaunchTombstoneReceiver(*config));
-  process_monitor.AddCommands(LaunchGnssGrpcProxyServerIfEnabled(*config));
-  process_monitor.AddCommands(LaunchSecureEnvironment(*config));
-  if (config->enable_host_bluetooth()) {
-    process_monitor.AddCommands(LaunchBluetoothConnector(*config));
-  }
-  process_monitor.AddCommands(LaunchVehicleHalServerIfEnabled(*config));
-  process_monitor.AddCommands(LaunchConsoleForwarderIfEnabled(*config));
-
-  // The streamer needs to launch before the VMM because it serves on several
-  // sockets (input devices, vsock frame server) when using crosvm.
-  if (config->enable_vnc_server()) {
-    process_monitor.AddCommands(LaunchVNCServer(*config));
-  }
-  if (config->enable_webrtc()) {
-    process_monitor.AddCommands(LaunchWebRTC(*config, webrtc_events_pipe));
+  for (auto& command_source : injector.getMultibindings<CommandSource>()) {
+    if (command_source->Enabled()) {
+      process_monitor_properties.AddCommands(command_source->Commands());
+    }
   }
 
-  // Start the guest VM
-  process_monitor.AddCommands(vm_manager->StartCommands(*config));
+  ProcessMonitor process_monitor(std::move(process_monitor_properties));
 
-  // Start other host processes
-  process_monitor.AddCommands(
-      LaunchSocketVsockProxyIfEnabled(*config, adbd_events_pipe));
-  process_monitor.AddCommands(LaunchAdbConnectorIfEnabled(*config));
+  CF_EXPECT(process_monitor.StartAndMonitorProcesses());
 
-  CHECK(process_monitor.StartAndMonitorProcesses())
-      << "Could not start subprocesses";
+  injector.get<ServerLoop&>().Run(process_monitor);  // Should not return
 
-  ServerLoop(launcher_monitor_socket, &process_monitor); // Should not return
-  LOG(ERROR) << "The server loop returned, it should never happen!!";
-
-  return RunnerExitCodes::kServerError;
+  return CF_ERR("The server loop returned, it should never happen!!");
 }
 
 } // namespace cuttlefish
 
 int main(int argc, char** argv) {
-  return cuttlefish::RunCvdMain(argc, argv);
+  auto result = cuttlefish::RunCvdMain(argc, argv);
+  CHECK(result.ok()) << result.error();
+  return 0;
 }
diff --git a/host/commands/run_cvd/process_monitor.cc b/host/commands/run_cvd/process_monitor.cc
index a4f7f72..ae720a8 100644
--- a/host/commands/run_cvd/process_monitor.cc
+++ b/host/commands/run_cvd/process_monitor.cc
@@ -26,6 +26,7 @@
 #include <stdio.h>
 
 #include <algorithm>
+#include <future>
 #include <thread>
 
 #include <android-base/logging.h>
@@ -39,86 +40,82 @@
   bool stop;
 };
 
-ProcessMonitor::ProcessMonitor(bool restart_subprocesses)
-    : restart_subprocesses_(restart_subprocesses), monitor_(-1) {
+ProcessMonitor::Properties& ProcessMonitor::Properties::RestartSubprocesses(
+    bool r) & {
+  restart_subprocesses_ = r;
+  return *this;
 }
 
-void ProcessMonitor::AddCommand(Command cmd) {
-  CHECK(monitor_ == -1) << "The monitor process is already running.";
-  CHECK(!monitor_socket_->IsOpen()) << "The monitor socket is already open.";
+ProcessMonitor::Properties ProcessMonitor::Properties::RestartSubprocesses(
+    bool r) && {
+  restart_subprocesses_ = r;
+  return std::move(*this);
+}
 
-  monitored_processes_.push_back(MonitorEntry());
-  auto& entry = monitored_processes_.back();
+ProcessMonitor::Properties& ProcessMonitor::Properties::AddCommand(
+    Command cmd) & {
+  auto& entry = entries_.emplace_back();
   entry.cmd.reset(new Command(std::move(cmd)));
+  return *this;
 }
 
-bool ProcessMonitor::StopMonitoredProcesses() {
-  if (monitor_ == -1) {
-    LOG(ERROR) << "The monitor process is already dead.";
-    return false;
-  }
-  if (!monitor_socket_->IsOpen()) {
-    LOG(ERROR) << "The monitor socket is already closed.";
-    return false;
-  }
+ProcessMonitor::Properties ProcessMonitor::Properties::AddCommand(
+    Command cmd) && {
+  auto& entry = entries_.emplace_back();
+  entry.cmd.reset(new Command(std::move(cmd)));
+  return std::move(*this);
+}
+
+ProcessMonitor::ProcessMonitor(ProcessMonitor::Properties&& properties)
+    : properties_(std::move(properties)), monitor_(-1) {}
+
+Result<void> ProcessMonitor::StopMonitoredProcesses() {
+  CF_EXPECT(monitor_ != -1, "The monitor process has already exited.");
+  CF_EXPECT(monitor_socket_->IsOpen(), "The monitor socket is already closed");
   ParentToChildMessage message;
   message.stop = true;
-  if (WriteAllBinary(monitor_socket_, &message) != sizeof(message)) {
-    LOG(ERROR) << "Failed to communicate with monitor socket: "
-                << monitor_socket_->StrError();
-    return false;
-  }
+  CF_EXPECT(WriteAllBinary(monitor_socket_, &message) == sizeof(message),
+            "Failed to communicate with monitor socket: "
+                << monitor_socket_->StrError());
+
   pid_t last_monitor = monitor_;
   monitor_ = -1;
   monitor_socket_->Close();
   int wstatus;
-  if (waitpid(last_monitor, &wstatus, 0) != last_monitor) {
-    LOG(ERROR) << "Failed to wait for monitor process";
-    return false;
-  }
-  if (WIFSIGNALED(wstatus)) {
-    LOG(ERROR) << "Monitor process exited due to a signal";
-    return false;
-  }
-  if (!WIFEXITED(wstatus)) {
-    LOG(ERROR) << "Monitor process exited for unknown reasons";
-    return false;
-  }
-  if (WEXITSTATUS(wstatus) != 0) {
-    LOG(ERROR) << "Monitor process exited with code " << WEXITSTATUS(wstatus);
-    return false;
-  }
-  return true;
+  CF_EXPECT(waitpid(last_monitor, &wstatus, 0) == last_monitor,
+            "Failed to wait for monitor process");
+  CF_EXPECT(!WIFSIGNALED(wstatus), "Monitor process exited due to a signal");
+  CF_EXPECT(WIFEXITED(wstatus), "Monitor process exited for unknown reasons");
+  CF_EXPECT(WEXITSTATUS(wstatus) == 0,
+            "Monitor process exited with code " << WEXITSTATUS(wstatus));
+  return {};
 }
 
-bool ProcessMonitor::StartAndMonitorProcesses() {
-  if (monitor_ != -1) {
-    LOG(ERROR) << "The monitor process was already started";
-    return false;
-  }
-  if (monitor_socket_->IsOpen()) {
-    LOG(ERROR) << "The monitor socket was already opened.";
-    return false;
-  }
+Result<void> ProcessMonitor::StartAndMonitorProcesses() {
+  CF_EXPECT(monitor_ == -1, "The monitor process was already started");
+  CF_EXPECT(!monitor_socket_->IsOpen(), "Monitor socket was already opened");
+
   SharedFD client_pipe, host_pipe;
-  if (!SharedFD::Pipe(&client_pipe, &host_pipe)) {
-    LOG(ERROR) << "Could not create the monitor socket.";
-    return false;
-  }
+  CF_EXPECT(SharedFD::Pipe(&client_pipe, &host_pipe),
+            "Could not create the monitor socket.");
   monitor_ = fork();
   if (monitor_ == 0) {
     monitor_socket_ = client_pipe;
     host_pipe->Close();
-    std::exit(MonitorRoutine() ? 0 : 1);
+    auto monitor = MonitorRoutine();
+    if (!monitor.ok()) {
+      LOG(ERROR) << "Monitoring processes failed:\n" << monitor.error();
+    }
+    std::exit(monitor.ok() ? 0 : 1);
   } else {
     client_pipe->Close();
     monitor_socket_ = host_pipe;
-    return true;
+    return {};
   }
 }
 
 static void LogSubprocessExit(const std::string& name, pid_t pid, int wstatus) {
-  LOG(INFO) << "Detected exit of monitored subprocess " << name;
+  LOG(INFO) << "Detected unexpected exit of monitored subprocess " << name;
   if (WIFEXITED(wstatus)) {
     LOG(INFO) << "Subprocess " << name << " (" << pid
               << ") has exited with exit code " << WEXITSTATUS(wstatus);
@@ -131,27 +128,43 @@
   }
 }
 
-bool ProcessMonitor::MonitorRoutine() {
+static void LogSubprocessExit(const std::string& name, const siginfo_t& infop) {
+  LOG(INFO) << "Detected unexpected exit of monitored subprocess " << name;
+  if (infop.si_code == CLD_EXITED) {
+    LOG(INFO) << "Subprocess " << name << " (" << infop.si_pid
+              << ") has exited with exit code " << infop.si_status;
+  } else if (infop.si_code == CLD_KILLED) {
+    LOG(ERROR) << "Subprocess " << name << " (" << infop.si_pid
+               << ") was interrupted by a signal: " << infop.si_status;
+  } else {
+    LOG(INFO) << "subprocess " << name << " (" << infop.si_pid
+              << ") has exited for unknown reasons (code = " << infop.si_code
+              << ", status = " << infop.si_status << ")";
+  }
+}
+
+Result<void> ProcessMonitor::MonitorRoutine() {
   // Make this process a subreaper to reliably catch subprocess exits.
   // See https://man7.org/linux/man-pages/man2/prctl.2.html
   prctl(PR_SET_CHILD_SUBREAPER, 1);
   prctl(PR_SET_PDEATHSIG, SIGHUP); // Die when parent dies
 
   LOG(DEBUG) << "Starting monitoring subprocesses";
-  for (auto& monitored : monitored_processes_) {
-    cuttlefish::SubprocessOptions options;
-    options.InGroup(true);
+  for (auto& monitored : properties_.entries_) {
+    LOG(INFO) << monitored.cmd->GetShortName();
+    auto options = SubprocessOptions().InGroup(true);
     monitored.proc.reset(new Subprocess(monitored.cmd->Start(options)));
-    CHECK(monitored.proc->Started()) << "Failed to start process";
+    CF_EXPECT(monitored.proc->Started(), "Failed to start process");
   }
 
   bool running = true;
-  std::thread parent_comms_thread([&running, this]() {
+  auto policy = std::launch::async;
+  auto parent_comms = std::async(policy, [&running, this]() -> Result<void> {
     LOG(DEBUG) << "Waiting for a `stop` message from the parent.";
     while (running) {
       ParentToChildMessage message;
-      CHECK(ReadExactBinary(monitor_socket_, &message) == sizeof(message))
-          << "Could not read message from parent.";
+      CF_EXPECT(ReadExactBinary(monitor_socket_, &message) == sizeof(message),
+                "Could not read message from parent.");
       if (message.stop) {
         running = false;
         // Wake up the wait() loop by giving it an exited child process
@@ -160,16 +173,17 @@
         }
       }
     }
+    return {};
   });
 
-  auto& monitored = monitored_processes_;
+  auto& monitored = properties_.entries_;
 
   LOG(DEBUG) << "Monitoring subprocesses";
   while(running) {
     int wstatus;
     pid_t pid = wait(&wstatus);
     int error_num = errno;
-    CHECK(pid != -1) << "Wait failed: " << strerror(error_num);
+    CF_EXPECT(pid != -1, "Wait failed: " << strerror(error_num));
     if (!WIFSIGNALED(wstatus) && !WIFEXITED(wstatus)) {
       LOG(DEBUG) << "Unexpected status from wait: " << wstatus
                   << " for pid " << pid;
@@ -184,35 +198,39 @@
       LogSubprocessExit("(unknown)", pid, wstatus);
     } else {
       LogSubprocessExit(it->cmd->GetShortName(), it->proc->pid(), wstatus);
-      if (restart_subprocesses_) {
-        cuttlefish::SubprocessOptions options;
-        options.InGroup(true);
+      if (properties_.restart_subprocesses_) {
+        auto options = SubprocessOptions().InGroup(true);
         it->proc.reset(new Subprocess(it->cmd->Start(options)));
       } else {
-        monitored_processes_.erase(it);
+        properties_.entries_.erase(it);
       }
     }
   }
 
-  parent_comms_thread.join(); // Should have exited if `running` is false
-  // Processes were started in the order they appear in the vector, stop them in
-  // reverse order for symmetry.
+  CF_EXPECT(parent_comms.get());  // Should have exited if `running` is false
   auto stop = [](const auto& it) {
-    if (!it.proc->Stop()) {
+    auto stop_result = it.proc->Stop();
+    if (stop_result == StopperResult::kStopFailure) {
       LOG(WARNING) << "Error in stopping \"" << it.cmd->GetShortName() << "\"";
       return false;
     }
-    int wstatus = 0;
-    auto ret = it.proc->Wait(&wstatus, 0);
-    if (ret < 0) {
+    siginfo_t infop;
+    auto success = it.proc->Wait(&infop, WEXITED);
+    if (success < 0) {
       LOG(WARNING) << "Failed to wait for process " << it.cmd->GetShortName();
       return false;
     }
+    if (stop_result == StopperResult::kStopCrash) {
+      LogSubprocessExit(it.cmd->GetShortName(), infop);
+    }
     return true;
   };
+  // Processes were started in the order they appear in the vector, stop them in
+  // reverse order for symmetry.
   size_t stopped = std::count_if(monitored.rbegin(), monitored.rend(), stop);
   LOG(DEBUG) << "Done monitoring subprocesses";
-  return stopped == monitored.size();
+  CF_EXPECT(stopped == monitored.size(), "Didn't stop all subprocesses");
+  return {};
 }
 
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/process_monitor.h b/host/commands/run_cvd/process_monitor.h
index 18b8ab8..c3f3127 100644
--- a/host/commands/run_cvd/process_monitor.h
+++ b/host/commands/run_cvd/process_monitor.h
@@ -20,13 +20,11 @@
 #include <thread>
 #include <vector>
 
-#include <common/libs/utils/subprocess.h>
+#include "common/libs/utils/result.h"
+#include "common/libs/utils/subprocess.h"
 
 namespace cuttlefish {
 
-struct MonitorEntry;
-using OnSocketReadyCb = std::function<bool(MonitorEntry*, int)>;
-
 struct MonitorEntry {
   std::unique_ptr<Command> cmd;
   std::unique_ptr<Subprocess> proc;
@@ -35,30 +33,49 @@
 // Keeps track of launched subprocesses, restarts them if they unexpectedly exit
 class ProcessMonitor {
  public:
-  ProcessMonitor(bool restart_subprocesses);
-  // Adds a command to the list of commands to be run and monitored. The
-  // callback will be called when the subprocess has ended.  If the callback
-  // returns false the subprocess will no longer be monitored. Can only be
-  // called before StartAndMonitorProcesses is called. OnSocketReadyCb will be
-  // called inside a forked process.
-  void AddCommand(Command cmd);
-  template <typename T>
-  void AddCommands(T&& commands) {
-    for (auto& command : commands) {
-      AddCommand(std::move(command));
+  class Properties {
+   public:
+    Properties& RestartSubprocesses(bool) &;
+    Properties RestartSubprocesses(bool) &&;
+
+    Properties& AddCommand(Command) &;
+    Properties AddCommand(Command) &&;
+
+    template <typename T>
+    Properties& AddCommands(T commands) & {
+      for (auto& command : commands) {
+        AddCommand(std::move(command));
+      }
+      return *this;
     }
-  }
+
+    template <typename T>
+    Properties AddCommands(T commands) && {
+      for (auto& command : commands) {
+        AddCommand(std::move(command));
+      }
+      return std::move(*this);
+    }
+
+   private:
+    bool restart_subprocesses_;
+    std::vector<MonitorEntry> entries_;
+
+    friend class ProcessMonitor;
+  };
+  ProcessMonitor(Properties&&);
 
   // Start all processes given by AddCommand.
-  bool StartAndMonitorProcesses();
+  Result<void> StartAndMonitorProcesses();
   // Stops all monitored subprocesses.
-  bool StopMonitoredProcesses();
- private:
-  bool MonitorRoutine();
+  Result<void> StopMonitoredProcesses();
 
-  bool restart_subprocesses_;
-  std::vector<MonitorEntry> monitored_processes_;
+ private:
+  Result<void> MonitorRoutine();
+
+  Properties properties_;
   pid_t monitor_;
   SharedFD monitor_socket_;
 };
+
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/reporting.cpp b/host/commands/run_cvd/reporting.cpp
new file mode 100644
index 0000000..db25185
--- /dev/null
+++ b/host/commands/run_cvd/reporting.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/run_cvd/reporting.h"
+
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+#include <string>
+#include <vector>
+
+namespace cuttlefish {
+
+static constexpr char kGreenColor[] = "\033[1;32m";
+static constexpr char kResetColor[] = "\033[0m";
+
+DiagnosticInformation::~DiagnosticInformation() = default;
+
+void DiagnosticInformation::PrintAll(
+    const std::vector<DiagnosticInformation*>& infos) {
+  LOG(INFO) << kGreenColor
+            << "The following files contain useful debugging information:"
+            << kResetColor;
+  for (const auto& info : infos) {
+    for (const auto& line : info->Diagnostics()) {
+      LOG(INFO) << kGreenColor << "  " << line << kResetColor;
+    }
+  }
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/run_cvd/reporting.h
similarity index 64%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/run_cvd/reporting.h
index 9f25445..f6b4d5a 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/run_cvd/reporting.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,20 @@
  * limitations under the License.
  */
 
-#include "common/libs/utils/size_utils.h"
+#pragma once
 
-#include <unistd.h>
+#include <fruit/fruit.h>
+#include <string>
+#include <vector>
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+class DiagnosticInformation {
+ public:
+  virtual ~DiagnosticInformation();
+  virtual std::vector<std::string> Diagnostics() const = 0;
+
+  static void PrintAll(const std::vector<DiagnosticInformation*>&);
+};
 
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/server_loop.cpp b/host/commands/run_cvd/server_loop.cpp
index 5e77b9d..dfe98ab 100644
--- a/host/commands/run_cvd/server_loop.cpp
+++ b/host/commands/run_cvd/server_loop.cpp
@@ -16,6 +16,7 @@
 
 #include "host/commands/run_cvd/server_loop.h"
 
+#include <fruit/fruit.h>
 #include <gflags/gflags.h>
 #include <unistd.h>
 #include <string>
@@ -26,6 +27,7 @@
 #include "host/commands/run_cvd/runner_defs.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/data_image.h"
+#include "host/libs/config/feature.h"
 
 namespace cuttlefish {
 
@@ -47,167 +49,214 @@
   return true;
 }
 
-void DeleteFifos(const CuttlefishConfig::InstanceSpecific& instance) {
-  // TODO(schuffelen): Create these FIFOs in assemble_cvd instead of run_cvd.
-  std::vector<std::string> pipes = {
-      instance.kernel_log_pipe_name(),
-      instance.console_in_pipe_name(),
-      instance.console_out_pipe_name(),
-      instance.logcat_pipe_name(),
-      instance.PerInstanceInternalPath("keymaster_fifo_vm.in"),
-      instance.PerInstanceInternalPath("keymaster_fifo_vm.out"),
-      instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"),
-      instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
-      instance.PerInstanceInternalPath("bt_fifo_vm.in"),
-      instance.PerInstanceInternalPath("bt_fifo_vm.out"),
-  };
-  for (const auto& pipe : pipes) {
-    unlink(pipe.c_str());
-  }
-}
+class ServerLoopImpl : public ServerLoop, public SetupFeature {
+ public:
+  INJECT(ServerLoopImpl(const CuttlefishConfig& config,
+                        const CuttlefishConfig::InstanceSpecific& instance))
+      : config_(config), instance_(instance) {}
 
-bool PowerwashFiles() {
-  auto config = CuttlefishConfig::Get();
-  if (!config) {
-    LOG(ERROR) << "Could not load the config.";
-    return false;
-  }
-  auto instance = config->ForDefaultInstance();
-
-  DeleteFifos(instance);
-
-  // TODO(schuffelen): Clean up duplication with assemble_cvd
-  auto kregistry_path = instance.access_kregistry_path();
-  unlink(kregistry_path.c_str());
-  CreateBlankImage(kregistry_path, 2 /* mb */, "none");
-
-  auto pstore_path = instance.pstore_path();
-  unlink(pstore_path.c_str());
-  CreateBlankImage(pstore_path, 2 /* mb */, "none");
-
-  auto sdcard_path = instance.sdcard_path();
-  auto sdcard_size = FileSize(sdcard_path);
-  unlink(sdcard_path.c_str());
-  // round up
-  auto sdcard_mb_size = (sdcard_size + (1 << 20) - 1) / (1 << 20);
-  LOG(DEBUG) << "Size in mb is " << sdcard_mb_size;
-  CreateBlankImage(sdcard_path, sdcard_mb_size, "sdcard");
-
-  auto overlay_path = instance.PerInstancePath("overlay.img");
-  unlink(overlay_path.c_str());
-  if (!CreateQcowOverlay(config->crosvm_binary(),
-                         instance.os_composite_disk_path(), overlay_path)) {
-    LOG(ERROR) << "CreateQcowOverlay failed";
-    return false;
-  }
-  return true;
-}
-
-void RestartRunCvd(const CuttlefishConfig& config, int notification_fd) {
-  auto config_path = config.AssemblyPath("cuttlefish_config.json");
-  auto followup_stdin = SharedFD::MemfdCreate("pseudo_stdin");
-  WriteAll(followup_stdin, config_path + "\n");
-  followup_stdin->LSeek(0, SEEK_SET);
-  followup_stdin->UNMANAGED_Dup2(0);
-
-  auto argv_vec = gflags::GetArgvs();
-  char** argv = new char*[argv_vec.size() + 2];
-  for (size_t i = 0; i < argv_vec.size(); i++) {
-    argv[i] = argv_vec[i].data();
-  }
-  // Will take precedence over any earlier arguments.
-  std::string reboot_notification =
-      "-reboot_notification_fd=" + std::to_string(notification_fd);
-  argv[argv_vec.size()] = reboot_notification.data();
-  argv[argv_vec.size() + 1] = nullptr;
-
-  execv("/proc/self/exe", argv);
-  // execve should not return, so something went wrong.
-  PLOG(ERROR) << "execv returned: ";
-}
-
-}  // namespace
-
-void ServerLoop(SharedFD server, ProcessMonitor* process_monitor) {
-  while (true) {
-    // TODO: use select to handle simultaneous connections.
-    auto client = SharedFD::Accept(*server);
-    LauncherAction action;
-    while (client->IsOpen() && client->Read(&action, sizeof(action)) > 0) {
-      switch (action) {
-        case LauncherAction::kStop:
-          if (process_monitor->StopMonitoredProcesses()) {
+  // ServerLoop
+  void Run(ProcessMonitor& process_monitor) override {
+    while (true) {
+      // TODO: use select to handle simultaneous connections.
+      auto client = SharedFD::Accept(*server_);
+      LauncherAction action;
+      while (client->IsOpen() && client->Read(&action, sizeof(action)) > 0) {
+        switch (action) {
+          case LauncherAction::kStop: {
+            auto stop = process_monitor.StopMonitoredProcesses();
+            if (stop.ok()) {
+              auto response = LauncherResponse::kSuccess;
+              client->Write(&response, sizeof(response));
+              std::exit(0);
+            } else {
+              LOG(ERROR) << "Failed to stop subprocesses:\n" << stop.error();
+              auto response = LauncherResponse::kError;
+              client->Write(&response, sizeof(response));
+            }
+            break;
+          }
+          case LauncherAction::kStatus: {
+            // TODO(schuffelen): Return more information on a side channel
             auto response = LauncherResponse::kSuccess;
             client->Write(&response, sizeof(response));
-            std::exit(0);
-          } else {
-            auto response = LauncherResponse::kError;
-            client->Write(&response, sizeof(response));
-          }
-          break;
-        case LauncherAction::kStatus: {
-          // TODO(schuffelen): Return more information on a side channel
-          auto response = LauncherResponse::kSuccess;
-          client->Write(&response, sizeof(response));
-          break;
-        }
-        case LauncherAction::kPowerwash: {
-          LOG(INFO) << "Received a Powerwash request from the monitor socket";
-          if (!process_monitor->StopMonitoredProcesses()) {
-            LOG(ERROR) << "Stopping processes failed.";
-            auto response = LauncherResponse::kError;
-            client->Write(&response, sizeof(response));
             break;
           }
-          if (!PowerwashFiles()) {
-            LOG(ERROR) << "Powerwashing files failed.";
-            auto response = LauncherResponse::kError;
+          case LauncherAction::kPowerwash: {
+            LOG(INFO) << "Received a Powerwash request from the monitor socket";
+            auto stop = process_monitor.StopMonitoredProcesses();
+            if (!stop.ok()) {
+              LOG(ERROR) << "Stopping processes failed:\n" << stop.error();
+              auto response = LauncherResponse::kError;
+              client->Write(&response, sizeof(response));
+              break;
+            }
+            if (!PowerwashFiles()) {
+              LOG(ERROR) << "Powerwashing files failed.";
+              auto response = LauncherResponse::kError;
+              client->Write(&response, sizeof(response));
+              break;
+            }
+            auto response = LauncherResponse::kSuccess;
             client->Write(&response, sizeof(response));
+
+            RestartRunCvd(client->UNMANAGED_Dup());
+            // RestartRunCvd should not return, so something went wrong.
+            response = LauncherResponse::kError;
+            client->Write(&response, sizeof(response));
+            LOG(FATAL) << "run_cvd in a bad state";
             break;
           }
-          auto response = LauncherResponse::kSuccess;
-          client->Write(&response, sizeof(response));
+          case LauncherAction::kRestart: {
+            auto stop = process_monitor.StopMonitoredProcesses();
+            if (!stop.ok()) {
+              LOG(ERROR) << "Stopping processes failed:\n" << stop.error();
+              auto response = LauncherResponse::kError;
+              client->Write(&response, sizeof(response));
+              break;
+            }
+            DeleteFifos();
 
-          auto config = CuttlefishConfig::Get();
-          CHECK(config) << "Could not load config";
-          RestartRunCvd(*config, client->UNMANAGED_Dup());
-          // RestartRunCvd should not return, so something went wrong.
-          response = LauncherResponse::kError;
-          client->Write(&response, sizeof(response));
-          LOG(FATAL) << "run_cvd in a bad state";
-          break;
-        }
-        case LauncherAction::kRestart: {
-          if (!process_monitor->StopMonitoredProcesses()) {
-            LOG(ERROR) << "Stopping processes failed.";
-            auto response = LauncherResponse::kError;
+            auto response = LauncherResponse::kSuccess;
             client->Write(&response, sizeof(response));
+            RestartRunCvd(client->UNMANAGED_Dup());
+            // RestartRunCvd should not return, so something went wrong.
+            response = LauncherResponse::kError;
+            client->Write(&response, sizeof(response));
+            LOG(FATAL) << "run_cvd in a bad state";
             break;
           }
-
-          auto config = CuttlefishConfig::Get();
-          CHECK(config) << "Could not load config";
-          auto instance = config->ForDefaultInstance();
-          DeleteFifos(instance);
-
-          auto response = LauncherResponse::kSuccess;
-          client->Write(&response, sizeof(response));
-          CHECK(config) << "Could not load config";
-          RestartRunCvd(*config, client->UNMANAGED_Dup());
-          // RestartRunCvd should not return, so something went wrong.
-          response = LauncherResponse::kError;
-          client->Write(&response, sizeof(response));
-          LOG(FATAL) << "run_cvd in a bad state";
-          break;
+          default:
+            LOG(ERROR) << "Unrecognized launcher action: "
+                       << static_cast<char>(action);
+            auto response = LauncherResponse::kError;
+            client->Write(&response, sizeof(response));
         }
-        default:
-          LOG(ERROR) << "Unrecognized launcher action: "
-                     << static_cast<char>(action);
-          auto response = LauncherResponse::kError;
-          client->Write(&response, sizeof(response));
       }
     }
   }
+
+  // SetupFeature
+  std::string Name() const override { return "ServerLoop"; }
+
+ private:
+  bool Enabled() const override { return true; }
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() {
+    auto launcher_monitor_path = instance_.launcher_monitor_socket_path();
+    server_ = SharedFD::SocketLocalServer(launcher_monitor_path.c_str(), false,
+                                          SOCK_STREAM, 0666);
+    if (!server_->IsOpen()) {
+      LOG(ERROR) << "Error when opening launcher server: "
+                 << server_->StrError();
+      return false;
+    }
+    return true;
+  }
+
+  void DeleteFifos() {
+    // TODO(schuffelen): Create these FIFOs in assemble_cvd instead of run_cvd.
+    std::vector<std::string> pipes = {
+        instance_.kernel_log_pipe_name(),
+        instance_.console_in_pipe_name(),
+        instance_.console_out_pipe_name(),
+        instance_.logcat_pipe_name(),
+        instance_.PerInstanceInternalPath("keymaster_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("keymaster_fifo_vm.out"),
+        instance_.PerInstanceInternalPath("gatekeeper_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
+        instance_.PerInstanceInternalPath("bt_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("bt_fifo_vm.out"),
+        instance_.PerInstanceInternalPath("gnsshvc_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("gnsshvc_fifo_vm.out"),
+        instance_.PerInstanceInternalPath("locationhvc_fifo_vm.in"),
+        instance_.PerInstanceInternalPath("locationhvc_fifo_vm.out"),
+    };
+    for (const auto& pipe : pipes) {
+      unlink(pipe.c_str());
+    }
+  }
+
+  bool PowerwashFiles() {
+    DeleteFifos();
+
+    // TODO(schuffelen): Clean up duplication with assemble_cvd
+    unlink(instance_.PerInstancePath("NVChip").c_str());
+
+    auto kregistry_path = instance_.access_kregistry_path();
+    unlink(kregistry_path.c_str());
+    CreateBlankImage(kregistry_path, 2 /* mb */, "none");
+
+    auto hwcomposer_pmem_path = instance_.hwcomposer_pmem_path();
+    unlink(hwcomposer_pmem_path.c_str());
+    CreateBlankImage(hwcomposer_pmem_path, 2 /* mb */, "none");
+
+    auto pstore_path = instance_.pstore_path();
+    unlink(pstore_path.c_str());
+    CreateBlankImage(pstore_path, 2 /* mb */, "none");
+
+    auto sdcard_path = instance_.sdcard_path();
+    auto sdcard_size = FileSize(sdcard_path);
+    unlink(sdcard_path.c_str());
+    // round up
+    auto sdcard_mb_size = (sdcard_size + (1 << 20) - 1) / (1 << 20);
+    LOG(DEBUG) << "Size in mb is " << sdcard_mb_size;
+    CreateBlankImage(sdcard_path, sdcard_mb_size, "sdcard");
+    std::vector<std::string> overlay_files{"overlay.img"};
+    if (instance_.start_ap()) {
+      overlay_files.emplace_back("ap_overlay.img");
+    }
+    for (auto overlay_file : {"overlay.img", "ap_overlay.img"}) {
+      auto overlay_path = instance_.PerInstancePath(overlay_file);
+      unlink(overlay_path.c_str());
+      if (!CreateQcowOverlay(config_.crosvm_binary(),
+                             config_.os_composite_disk_path(), overlay_path)) {
+        LOG(ERROR) << "CreateQcowOverlay failed";
+        return false;
+      }
+    }
+    return true;
+  }
+
+  void RestartRunCvd(int notification_fd) {
+    auto config_path = config_.AssemblyPath("cuttlefish_config.json");
+    auto followup_stdin = SharedFD::MemfdCreate("pseudo_stdin");
+    WriteAll(followup_stdin, config_path + "\n");
+    followup_stdin->LSeek(0, SEEK_SET);
+    followup_stdin->UNMANAGED_Dup2(0);
+
+    auto argv_vec = gflags::GetArgvs();
+    char** argv = new char*[argv_vec.size() + 2];
+    for (size_t i = 0; i < argv_vec.size(); i++) {
+      argv[i] = argv_vec[i].data();
+    }
+    // Will take precedence over any earlier arguments.
+    std::string reboot_notification =
+        "-reboot_notification_fd=" + std::to_string(notification_fd);
+    argv[argv_vec.size()] = reboot_notification.data();
+    argv[argv_vec.size() + 1] = nullptr;
+
+    execv("/proc/self/exe", argv);
+    // execve should not return, so something went wrong.
+    PLOG(ERROR) << "execv returned: ";
+  }
+
+  const CuttlefishConfig& config_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  SharedFD server_;
+};
+
+}  // namespace
+
+ServerLoop::~ServerLoop() = default;
+
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>,
+                 ServerLoop>
+serverLoopComponent() {
+  return fruit::createComponent()
+      .bind<ServerLoop, ServerLoopImpl>()
+      .addMultibinding<SetupFeature, ServerLoopImpl>();
 }
 
 }  // namespace cuttlefish
diff --git a/host/commands/run_cvd/server_loop.h b/host/commands/run_cvd/server_loop.h
index 327aff4..2364cb9 100644
--- a/host/commands/run_cvd/server_loop.h
+++ b/host/commands/run_cvd/server_loop.h
@@ -16,11 +16,22 @@
 
 #pragma once
 
+#include <fruit/fruit.h>
+
 #include "common/libs/fs/shared_fd.h"
 #include "host/commands/run_cvd/process_monitor.h"
+#include "host/libs/config/cuttlefish_config.h"
 
 namespace cuttlefish {
 
-void ServerLoop(SharedFD server, ProcessMonitor* process_monitor);
+class ServerLoop {
+ public:
+  virtual ~ServerLoop();
+  virtual void Run(ProcessMonitor& process_monitor) = 0;
+};
 
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>,
+                 ServerLoop>
+serverLoopComponent();
 }
diff --git a/host/commands/run_cvd/validate.cpp b/host/commands/run_cvd/validate.cpp
new file mode 100644
index 0000000..fb55cbb
--- /dev/null
+++ b/host/commands/run_cvd/validate.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+#include <iostream>
+
+#include "common/libs/utils/network.h"
+#include "common/libs/utils/result.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
+#include "host/libs/vm_manager/host_configuration.h"
+
+namespace cuttlefish {
+namespace {
+
+using vm_manager::ValidateHostConfiguration;
+
+class ValidateTapDevices : public SetupFeature {
+ public:
+  INJECT(ValidateTapDevices(const CuttlefishConfig::InstanceSpecific& instance))
+      : instance_(instance) {}
+
+  std::string Name() const override { return "ValidateTapDevices"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  Result<void> ResultSetup() override {
+    auto taps = TapInterfacesInUse();
+    auto wifi = instance_.wifi_tap_name();
+    CF_EXPECT(taps.count(wifi) == 0, "Device \"" << wifi << "\" in use");
+    auto mobile = instance_.mobile_tap_name();
+    CF_EXPECT(taps.count(mobile) == 0, "Device \"" << mobile << "\" in use");
+    auto eth = instance_.ethernet_tap_name();
+    CF_EXPECT(taps.count(eth) == 0, "Device \"" << eth << "\" in use");
+    return {};
+  }
+
+ private:
+  const CuttlefishConfig::InstanceSpecific& instance_;
+};
+
+class ValidateHostConfigurationFeature : public SetupFeature {
+ public:
+  INJECT(ValidateHostConfigurationFeature()) {}
+
+  bool Enabled() const override {
+#ifndef __ANDROID__
+    return true;
+#else
+    return false;
+#endif
+  }
+  std::string Name() const override { return "ValidateHostConfiguration"; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override {
+    // Check host configuration
+    std::vector<std::string> config_commands;
+    if (!ValidateHostConfiguration(&config_commands)) {
+      LOG(ERROR) << "Validation of user configuration failed";
+      std::cout << "Execute the following to correctly configure:" << std::endl;
+      for (auto& command : config_commands) {
+        std::cout << "  " << command << std::endl;
+      }
+      std::cout << "You may need to logout for the changes to take effect"
+                << std::endl;
+      return false;
+    }
+    return true;
+  }
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<const CuttlefishConfig::InstanceSpecific>>
+validationComponent() {
+  return fruit::createComponent()
+      .addMultibinding<SetupFeature, ValidateHostConfigurationFeature>()
+      .addMultibinding<SetupFeature, ValidateTapDevices>();
+}
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/run_cvd/validate.h
similarity index 63%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/run_cvd/validate.h
index 9f25445..99c5c07 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/run_cvd/validate.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-#include "common/libs/utils/size_utils.h"
+#pragma once
 
-#include <unistd.h>
+#include <fruit/fruit.h>
+
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+fruit::Component<fruit::Required<const CuttlefishConfig,
+                                 const CuttlefishConfig::InstanceSpecific>>
+validationComponent();
 
-}  // namespace cuttlefish
+}
diff --git a/host/commands/secure_env/Android.bp b/host/commands/secure_env/Android.bp
index 3ceeda5..cd6e5a0 100644
--- a/host/commands/secure_env/Android.bp
+++ b/host/commands/secure_env/Android.bp
@@ -17,10 +17,48 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-cc_binary_host {
-    name: "secure_env",
+cc_defaults {
+    name: "secure_env_defaults",
+    shared_libs: [
+        "libext2_blkid",
+        "libbase",
+        "libcppbor_external",
+        "libcppcose_rkp",
+        "libcuttlefish_fs",
+        "libcuttlefish_kernel_log_monitor_utils",
+        "libcuttlefish_security",
+        "libcuttlefish_utils",
+        "libfruit",
+        "libgatekeeper",
+        "libjsoncpp",
+        "libkeymaster_portable",
+        "libkeymaster_messages",
+        "libsoft_attestation_cert",
+        "liblog",
+        "libcrypto",
+        "libcutils",
+        "libpuresoftkeymasterdevice_host",
+        "ms-tpm-20-ref-lib",
+        "tpm2-tss2-esys",
+        "tpm2-tss2-mu",
+        "tpm2-tss2-rc",
+        "tpm2-tss2-tcti",
+    ],
+    static_libs: [
+        "libcuttlefish_host_config",
+        "libgflags",
+        "libscrypt_static",
+    ],
+    cflags: [
+        "-fno-rtti", // Required for libkeymaster_portable
+    ],
+}
+
+cc_library_host_static {
+    name: "libsecure_env",
     srcs: [
         "composite_serialization.cpp",
+        "confui_sign_server.cpp",
         "device_tpm.cpp",
         "encrypted_serializable.cpp",
         "fragile_tpm_storage.cpp",
@@ -42,36 +80,35 @@
         "tpm_keymaster_context.cpp",
         "tpm_keymaster_enforcement.cpp",
         "tpm_random_source.cpp",
+        "tpm_remote_provisioning_context.cpp",
         "tpm_resource_manager.cpp",
         "tpm_serialize.cpp",
     ],
-    shared_libs: [
-        "libbase",
-        "libcuttlefish_fs",
-        "libcuttlefish_security",
-        "libcuttlefish_utils",
-        "libgatekeeper",
-        "libjsoncpp",
-        "libkeymaster_portable",
-        "libkeymaster_messages",
-        "libsoft_attestation_cert",
-        "liblog",
-        "libcrypto",
-        "libcutils",
-        "libpuresoftkeymasterdevice_host",
-        "ms-tpm-20-ref-lib",
-        "tpm2-tss2-esys",
-        "tpm2-tss2-mu",
-        "tpm2-tss2-rc",
-        "tpm2-tss2-tcti",
+    defaults: ["cuttlefish_buildhost_only", "secure_env_defaults"],
+}
+
+cc_binary_host {
+    name: "secure_env",
+    srcs: [
+        "secure_env.cpp",
     ],
     static_libs: [
-        "libcuttlefish_host_config",
-        "libgflags",
-        "libscrypt_static",
+        "libsecure_env",
     ],
-    defaults: ["cuttlefish_buildhost_only"],
-    cflags: [
-        "-fno-rtti", // Required for libkeymaster_portable
+    defaults: ["cuttlefish_buildhost_only", "secure_env_defaults"],
+}
+
+cc_test_host {
+    name: "libsecure_env_test",
+    srcs: [
+        "test_tpm.cpp",
+        "encrypted_serializable_test.cpp",
     ],
+    static_libs: [
+        "libsecure_env",
+    ],
+    defaults: ["cuttlefish_buildhost_only", "secure_env_defaults"],
+    test_options: {
+        unit_test: true,
+    },
 }
diff --git a/host/commands/secure_env/composite_serialization.cpp b/host/commands/secure_env/composite_serialization.cpp
index e3d6e43..791a604 100644
--- a/host/commands/secure_env/composite_serialization.cpp
+++ b/host/commands/secure_env/composite_serialization.cpp
@@ -17,6 +17,8 @@
 
 using keymaster::Serializable;
 
+namespace cuttlefish {
+
 CompositeSerializable::CompositeSerializable(
     const std::vector<Serializable*>& members) : members_(members) {
 }
@@ -46,3 +48,5 @@
   }
   return true;
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/composite_serialization.h b/host/commands/secure_env/composite_serialization.h
index dfe2883..c83d159 100644
--- a/host/commands/secure_env/composite_serialization.h
+++ b/host/commands/secure_env/composite_serialization.h
@@ -19,6 +19,8 @@
 
 #include "keymaster/serializable.h"
 
+namespace cuttlefish {
+
 /**
  * A keymaster::Serializable type that refers to multiple other
  * keymaster::Serializable instances by pointer. When data is serialized or
@@ -37,3 +39,5 @@
 private:
   std::vector<keymaster::Serializable*> members_;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/confui_sign_server.cpp b/host/commands/secure_env/confui_sign_server.cpp
new file mode 100644
index 0000000..618f3e2
--- /dev/null
+++ b/host/commands/secure_env/confui_sign_server.cpp
@@ -0,0 +1,90 @@
+//
+// 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.
+
+#include "confui_sign_server.h"
+
+#include <android-base/logging.h>
+
+#include "host/commands/secure_env/primary_key_builder.h"
+#include "host/commands/secure_env/tpm_hmac.h"
+#include "host/libs/config/cuttlefish_config.h"
+
+namespace cuttlefish {
+ConfUiSignServer::ConfUiSignServer(TpmResourceManager& tpm_resource_manager,
+                                   SharedFD server_fd)
+    : tpm_resource_manager_(tpm_resource_manager), server_fd_(server_fd) {
+  auto config = cuttlefish::CuttlefishConfig::Get();
+  CHECK(config) << "Config must not be null";
+  auto instance = config->ForDefaultInstance();
+  server_socket_path_ = instance.PerInstanceInternalPath("confui_sign.sock");
+}
+
+[[noreturn]] void ConfUiSignServer::MainLoop() {
+  while (true) {
+    if (!server_fd_->IsOpen()) {
+      server_fd_ = SharedFD::SocketLocalServer(server_socket_path_, false,
+                                               SOCK_STREAM, 0600);
+    }
+    auto accepted_socket_fd = SharedFD::Accept(*server_fd_);
+    if (!accepted_socket_fd->IsOpen()) {
+      LOG(ERROR) << "Confirmation UI host signing client socket is broken.";
+      continue;
+    }
+    ConfUiSignSender sign_sender(accepted_socket_fd);
+
+    // receive request
+    auto request_opt = sign_sender.Receive();
+    if (!request_opt) {
+      std::string error_category = (sign_sender.IsIoError() ? "IO" : "Logic");
+      LOG(ERROR) << "ReceiveRequest failed with " << error_category << " error";
+      continue;
+    }
+    auto request = request_opt.value();
+
+    // get signing key
+    auto signing_key_builder = PrimaryKeyBuilder();
+    signing_key_builder.SigningKey();
+    signing_key_builder.UniqueData("confirmation_token");
+    auto signing_key = signing_key_builder.CreateKey(tpm_resource_manager_);
+    if (!signing_key) {
+      LOG(ERROR) << "Could not generate signing key";
+      sign_sender.Send(confui::SignMessageError::kUnknownError, {});
+      continue;
+    }
+
+    // hmac
+    auto hmac = TpmHmac(tpm_resource_manager_, signing_key->get(),
+                        TpmAuth(ESYS_TR_PASSWORD), request.payload_.data(),
+                        request.payload_.size());
+    if (!hmac) {
+      LOG(ERROR) << "Could not calculate confirmation token hmac";
+      sign_sender.Send(confui::SignMessageError::kUnknownError, {});
+      continue;
+    }
+    if (hmac->size == 0) {
+      LOG(ERROR) << "hmac was too short";
+      sign_sender.Send(confui::SignMessageError::kUnknownError, {});
+      continue;
+    }
+
+    // send hmac
+    std::vector<std::uint8_t> hmac_buffer(hmac->buffer,
+                                          hmac->buffer + hmac->size);
+    if (!sign_sender.Send(confui::SignMessageError::kOk, hmac_buffer)) {
+      LOG(ERROR) << "Sending signature failed likely due to I/O error";
+    }
+  }
+}
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/confui_sign_server.h b/host/commands/secure_env/confui_sign_server.h
new file mode 100644
index 0000000..531a3ed
--- /dev/null
+++ b/host/commands/secure_env/confui_sign_server.h
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/security/confui_sign.h"
+#include "host/commands/secure_env/tpm_resource_manager.h"
+
+namespace cuttlefish {
+class ConfUiSignServer {
+ public:
+  ConfUiSignServer(TpmResourceManager& tpm_resource_manager,
+                   SharedFD server_fd);
+  [[noreturn]] void MainLoop();
+
+ private:
+  TpmResourceManager& tpm_resource_manager_;
+  std::string server_socket_path_;
+  SharedFD server_fd_;
+};
+}  // end of namespace cuttlefish
diff --git a/host/commands/secure_env/device_tpm.cpp b/host/commands/secure_env/device_tpm.cpp
index 6e4b4be..81ffbf3 100644
--- a/host/commands/secure_env/device_tpm.cpp
+++ b/host/commands/secure_env/device_tpm.cpp
@@ -20,6 +20,8 @@
 #include <tss2/tss2_tcti.h>
 #include <tss2/tss2_tcti_device.h>
 
+namespace cuttlefish {
+
 static void FinalizeTcti(TSS2_TCTI_CONTEXT* tcti_context) {
   if (tcti_context == nullptr) {
     return;
@@ -52,3 +54,5 @@
 TSS2_TCTI_CONTEXT* DeviceTpm::TctiContext() {
   return tpm_.get();
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/device_tpm.h b/host/commands/secure_env/device_tpm.h
index a52666e..48988ad 100644
--- a/host/commands/secure_env/device_tpm.h
+++ b/host/commands/secure_env/device_tpm.h
@@ -21,6 +21,8 @@
 
 #include "host/commands/secure_env/tpm.h"
 
+namespace cuttlefish {
+
 /*
  * Exposes a TSS2_TCTI_CONTEXT for interacting with a TPM device node.
  */
@@ -33,3 +35,5 @@
 private:
   std::unique_ptr<TSS2_TCTI_CONTEXT, void(*)(TSS2_TCTI_CONTEXT*)> tpm_;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/encrypted_serializable.cpp b/host/commands/secure_env/encrypted_serializable.cpp
index 3845212..75891fc 100644
--- a/host/commands/secure_env/encrypted_serializable.cpp
+++ b/host/commands/secure_env/encrypted_serializable.cpp
@@ -16,13 +16,16 @@
 #include "encrypted_serializable.h"
 
 #include <vector>
-
+//
 #include <android-base/logging.h>
 
 #include "host/commands/secure_env/tpm_auth.h"
 #include "host/commands/secure_env/tpm_encrypt_decrypt.h"
+#include "host/commands/secure_env/tpm_random_source.h"
 #include "host/commands/secure_env/tpm_serialize.h"
 
+namespace cuttlefish {
+
 EncryptedSerializable::EncryptedSerializable(
     TpmResourceManager& resource_manager,
     std::function<TpmObjectSlot(TpmResourceManager&)> parent_key_fn,
@@ -172,11 +175,14 @@
   SerializeTpmKeyPublic serialize_public(&key_public);
   SerializeTpmKeyPrivate serialize_private(&key_private);
   auto encrypted_size = RoundUpToBlockSize(wrapped_.SerializedSize());
-  return serialize_public.SerializedSize()
-    + serialize_private.SerializedSize()
-    + sizeof(uint32_t)
-    + sizeof(uint32_t)
-    + encrypted_size;
+  size_t size = serialize_public.SerializedSize();  // tpm key public part
+  size += serialize_private.SerializedSize();       // tpm key private part
+  size += sizeof(uint32_t);                         // block size
+  size += sizeof(uint32_t);         // initialization vector length
+  size += sizeof(((TPM2B_IV*)nullptr)->buffer);  // initialization vector
+  size += sizeof(uint32_t);         // wrapped size
+  size += encrypted_size;           // encrypted data
+  return size;
 }
 
 uint8_t* EncryptedSerializable::Serialize(
@@ -195,6 +201,15 @@
     return buf;
   }
 
+  TPM2B_IV iv;
+  iv.size = sizeof(iv.buffer);
+  auto rc = TpmRandomSource(resource_manager_.Esys())
+                .GenerateRandom(iv.buffer, sizeof(iv.buffer));
+  if (rc != KM_ERROR_OK) {
+    LOG(ERROR) << "Failed to get random data";
+    return buf;
+  }
+
   auto wrapped_size = wrapped_.SerializedSize();
   auto encrypted_size = RoundUpToBlockSize(wrapped_size);
   std::vector<uint8_t> unencrypted(encrypted_size + 1, 0);
@@ -206,13 +221,9 @@
     return buf;
   }
   std::vector<uint8_t> encrypted(encrypted_size, 0);
-  if (!TpmEncrypt(
-      resource_manager_.Esys(),
-      key_slot->get(),
-      TpmAuth(ESYS_TR_PASSWORD),
-      unencrypted.data(),
-      encrypted.data(),
-      encrypted_size)) {
+  if (!TpmEncrypt(  //
+          resource_manager_.Esys(), key_slot->get(), TpmAuth(ESYS_TR_PASSWORD),
+          iv, unencrypted.data(), encrypted.data(), encrypted_size)) {
     LOG(ERROR) << "Encryption failed";
     return buf;
   }
@@ -222,6 +233,8 @@
   buf = serialize_public.Serialize(buf, end);
   buf = serialize_private.Serialize(buf, end);
   buf = keymaster::append_uint32_to_buf(buf, end, BLOCK_SIZE);
+  buf = keymaster::append_uint32_to_buf(buf, end, iv.size);
+  buf = keymaster::append_to_buf(buf, end, iv.buffer, iv.size);
   buf = keymaster::append_uint32_to_buf(buf, end, wrapped_size);
   buf = keymaster::append_to_buf(buf, end, encrypted.data(), encrypted_size);
   return buf;
@@ -262,6 +275,22 @@
                << ", expected " << BLOCK_SIZE;
     return false;
   }
+  uint32_t iv_size = 0;
+  if (!keymaster::copy_uint32_from_buf(buf_ptr, end, &iv_size)) {
+    LOG(ERROR) << "Failed to read iv size";
+    return false;
+  }
+  TPM2B_IV iv;
+  if (iv_size != sizeof(iv.buffer)) {
+    LOG(ERROR) << "iv size mismatch: received " << iv_size << ", expected "
+               << sizeof(iv.buffer);
+    return false;
+  }
+  iv.size = sizeof(iv.buffer);
+  if (!keymaster::copy_from_buf(buf_ptr, end, iv.buffer, sizeof(iv.buffer))) {
+    LOG(ERROR) << "Failed to read wrapped size";
+    return false;
+  }
   uint32_t wrapped_size = 0;
   if (!keymaster::copy_uint32_from_buf(buf_ptr, end, &wrapped_size)) {
     LOG(ERROR) << "Failed to read wrapped size";
@@ -275,13 +304,9 @@
     return false;
   }
   std::vector<uint8_t> decrypted_data(encrypted_size, 0);
-  if (!TpmDecrypt(
-      resource_manager_.Esys(),
-      key_slot->get(),
-      TpmAuth(ESYS_TR_PASSWORD),
-      encrypted_data.data(),
-      decrypted_data.data(),
-      encrypted_size)) {
+  if (!TpmDecrypt(  //
+          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;
   }
@@ -298,3 +323,5 @@
   }
   return true;
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/encrypted_serializable.h b/host/commands/secure_env/encrypted_serializable.h
index c4a2e08..336c40c 100644
--- a/host/commands/secure_env/encrypted_serializable.h
+++ b/host/commands/secure_env/encrypted_serializable.h
@@ -19,6 +19,8 @@
 
 #include "host/commands/secure_env/tpm_resource_manager.h"
 
+namespace cuttlefish {
+
 /**
  * A keymaster::Serializable that wraps another keymaster::Serializable,
  * encrypting the data with a TPM to ensure privacy.
@@ -57,3 +59,5 @@
   std::function<TpmObjectSlot(TpmResourceManager&)> parent_key_fn_;
   keymaster::Serializable& wrapped_;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/encrypted_serializable_test.cpp b/host/commands/secure_env/encrypted_serializable_test.cpp
new file mode 100644
index 0000000..f1e6923
--- /dev/null
+++ b/host/commands/secure_env/encrypted_serializable_test.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/secure_env/encrypted_serializable.h"
+
+#include <gtest/gtest.h>
+#include <keymaster/serializable.h>
+#include <string.h>
+
+#include "host/commands/secure_env/primary_key_builder.h"
+#include "host/commands/secure_env/test_tpm.h"
+#include "host/commands/secure_env/tpm_resource_manager.h"
+
+namespace cuttlefish {
+
+TEST(TpmEncryptedSerializable, BinaryData) {
+  TestTpm tpm;
+  TpmResourceManager resource_manager(tpm.Esys());
+
+  uint8_t input_data[] = {1, 2, 3, 4, 5};
+  keymaster::Buffer input(input_data, sizeof(input_data));
+  EncryptedSerializable encrypt_input(resource_manager,
+                                      ParentKeyCreator("test"), input);
+
+  std::vector<uint8_t> encrypted_data(encrypt_input.SerializedSize());
+  auto encrypt_return = encrypt_input.Serialize(
+      encrypted_data.data(), encrypted_data.data() + encrypted_data.size());
+
+  keymaster::Buffer output(sizeof(input_data));
+  EncryptedSerializable decrypt_intermediate(resource_manager,
+                                             ParentKeyCreator("test"), output);
+  const uint8_t* encrypted_data_ptr = encrypted_data.data();
+  auto decrypt_return = decrypt_intermediate.Deserialize(
+      &encrypted_data_ptr, encrypted_data_ptr + encrypted_data.size());
+
+  ASSERT_EQ(encrypt_return, encrypted_data.data() + encrypted_data.size());
+  ASSERT_TRUE(decrypt_return);
+  ASSERT_EQ(encrypted_data_ptr, encrypted_data.data() + encrypted_data.size());
+  ASSERT_EQ(0, memcmp(input_data, output.begin(), sizeof(input_data)));
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/fragile_tpm_storage.cpp b/host/commands/secure_env/fragile_tpm_storage.cpp
index ae66164..107d895 100644
--- a/host/commands/secure_env/fragile_tpm_storage.cpp
+++ b/host/commands/secure_env/fragile_tpm_storage.cpp
@@ -23,6 +23,8 @@
 #include "host/commands/secure_env/json_serializable.h"
 #include "host/commands/secure_env/tpm_random_source.h"
 
+namespace cuttlefish {
+
 static constexpr char kEntries[] = "entries";
 static constexpr char kKey[] = "key";
 static constexpr char kHandle[] = "handle";
@@ -238,3 +240,5 @@
   }
   return true;
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/fragile_tpm_storage.h b/host/commands/secure_env/fragile_tpm_storage.h
index b52b373..9a92910 100644
--- a/host/commands/secure_env/fragile_tpm_storage.h
+++ b/host/commands/secure_env/fragile_tpm_storage.h
@@ -26,6 +26,8 @@
 #include "host/commands/secure_env/gatekeeper_storage.h"
 #include "host/commands/secure_env/tpm_resource_manager.h"
 
+namespace cuttlefish {
+
 /**
  * Manager for data stored inside the TPM with an index outside of the TPM. The
  * contents of the data cannot be corrupted or decrypted by accessing the index,
@@ -58,3 +60,5 @@
   std::string index_file_;
   Json::Value index_;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/gatekeeper_responder.cpp b/host/commands/secure_env/gatekeeper_responder.cpp
index 7d43a18..756bb72 100644
--- a/host/commands/secure_env/gatekeeper_responder.cpp
+++ b/host/commands/secure_env/gatekeeper_responder.cpp
@@ -18,6 +18,8 @@
 #include <android-base/logging.h>
 #include <gatekeeper/gatekeeper_messages.h>
 
+namespace cuttlefish {
+
 GatekeeperResponder::GatekeeperResponder(
     cuttlefish::GatekeeperChannel& channel, gatekeeper::GateKeeper& gatekeeper)
     : channel_(channel), gatekeeper_(gatekeeper) {
@@ -60,3 +62,5 @@
       return false;
   }
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/gatekeeper_responder.h b/host/commands/secure_env/gatekeeper_responder.h
index bd27955..fcf7b28 100644
--- a/host/commands/secure_env/gatekeeper_responder.h
+++ b/host/commands/secure_env/gatekeeper_responder.h
@@ -19,6 +19,8 @@
 
 #include "common/libs/security/gatekeeper_channel.h"
 
+namespace cuttlefish {
+
 class GatekeeperResponder {
 private:
   cuttlefish::GatekeeperChannel& channel_;
@@ -29,3 +31,5 @@
 
   bool ProcessMessage();
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/gatekeeper_storage.h b/host/commands/secure_env/gatekeeper_storage.h
index d272f71..1882ab0 100644
--- a/host/commands/secure_env/gatekeeper_storage.h
+++ b/host/commands/secure_env/gatekeeper_storage.h
@@ -20,6 +20,8 @@
 #include <json/json.h>
 #include <tss2/tss2_tpm2_types.h>
 
+namespace cuttlefish {
+
 /**
  * Data storage tailored to Gatekeeper's storage needs: storing binary blobs
  * that can be destroyed without a trace or corrupted with an obvious trace, but
@@ -40,3 +42,5 @@
   virtual bool Write(const Json::Value& key, const TPM2B_MAX_NV_BUFFER& data)
       = 0;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/hmac_serializable.cpp b/host/commands/secure_env/hmac_serializable.cpp
index c40b736..9366797 100644
--- a/host/commands/secure_env/hmac_serializable.cpp
+++ b/host/commands/secure_env/hmac_serializable.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2020 The Android Open Source Project
+// cOpyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -16,20 +16,23 @@
 #include "hmac_serializable.h"
 
 #include <android-base/logging.h>
+#include <optional>
+#include <vector>
 
 #include "host/commands/secure_env/tpm_auth.h"
 #include "host/commands/secure_env/tpm_hmac.h"
 
+namespace cuttlefish {
+
 HmacSerializable::HmacSerializable(
     TpmResourceManager& resource_manager,
     std::function<TpmObjectSlot(TpmResourceManager&)> signing_key_fn,
-    uint32_t digest_size,
-    Serializable* wrapped) :
-    resource_manager_(resource_manager),
-    signing_key_fn_(signing_key_fn),
-    digest_size_(digest_size),
-    wrapped_(wrapped) {
-}
+    uint32_t digest_size, Serializable* wrapped, const Serializable* aad)
+    : resource_manager_(resource_manager),
+      signing_key_fn_(signing_key_fn),
+      digest_size_(digest_size),
+      wrapped_(wrapped),
+      aad_(aad) {}
 
 size_t HmacSerializable::SerializedSize() const {
   auto digest_size = sizeof(uint32_t) + digest_size_;
@@ -51,13 +54,13 @@
     LOG(ERROR) << "Could not retrieve key";
     return buf;
   }
+  auto maced_data = AppendAad(signed_data, wrapped_size);
+  if (!maced_data) {
+    return buf;
+  }
   auto hmac_data =
-    TpmHmac(
-        resource_manager_,
-        key->get(),
-        TpmAuth(ESYS_TR_PASSWORD),
-        signed_data,
-        wrapped_size);
+      TpmHmac(resource_manager_, key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              maced_data->data(), maced_data->size());
   if (!hmac_data) {
     LOG(ERROR) << "Failed to produce hmac";
     return buf;
@@ -99,13 +102,13 @@
     LOG(ERROR) << "Could not retrieve key";
     return false;
   }
+  auto maced_data = AppendAad(signed_data.get(), signed_data_size);
+  if (!maced_data) {
+    return false;
+  }
   auto hmac_check =
-    TpmHmac(
-        resource_manager_,
-        key->get(),
-        TpmAuth(ESYS_TR_PASSWORD),
-        signed_data.get(),
-        signed_data_size);
+      TpmHmac(resource_manager_, key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              maced_data->data(), maced_data->size());
   if (!hmac_check) {
     LOG(ERROR) << "Unable to calculate signature check";
     return false;
@@ -125,3 +128,24 @@
   return wrapped_->Deserialize(
       const_cast<const uint8_t**>(&inner_buf), inner_buf_end);
 }
+
+std::optional<std::vector<uint8_t>> HmacSerializable::AppendAad(
+    const uint8_t* sensitive, size_t sensitive_size) const {
+  if (!aad_) {
+    return std::vector<uint8_t>(sensitive, sensitive + sensitive_size);
+  }
+  std::vector<uint8_t> output(sensitive_size + aad_->SerializedSize());
+  std::copy(sensitive, sensitive + sensitive_size, output.begin());
+
+  const uint8_t* actual_output_end =
+      aad_->Serialize(&output[sensitive_size], output.data() + output.size());
+  const ptrdiff_t actual_aad_size = actual_output_end - output.data();
+  if (actual_aad_size != output.size()) {
+    LOG(ERROR) << "Serialized aad did not match expected size. Expected: "
+               << output.size() << ", actual: " << actual_aad_size;
+    return std::nullopt;
+  }
+  return output;
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/hmac_serializable.h b/host/commands/secure_env/hmac_serializable.h
index 4b90124..35a6236 100644
--- a/host/commands/secure_env/hmac_serializable.h
+++ b/host/commands/secure_env/hmac_serializable.h
@@ -15,10 +15,15 @@
 
 #pragma once
 
+#include <optional>
+#include <vector>
+
 #include <keymaster/serializable.h>
 
 #include "host/commands/secure_env/tpm_resource_manager.h"
 
+namespace cuttlefish {
+
 /**
  * A keymaster::Serializable that wraps another keymaster::Serializable,
  * protecting it from tampering while it is stored elsewhere. This stores
@@ -38,17 +43,23 @@
  */
 class HmacSerializable : public keymaster::Serializable {
 public:
-  HmacSerializable(TpmResourceManager&,
-                   std::function<TpmObjectSlot(TpmResourceManager&)>,
-                   uint32_t digest_size,
-                   Serializable*);
+ HmacSerializable(TpmResourceManager&,
+                  std::function<TpmObjectSlot(TpmResourceManager&)>,
+                  uint32_t digest_size, Serializable*, const Serializable* aad);
 
-  size_t SerializedSize() const override;
-  uint8_t* Serialize(uint8_t* buf, const uint8_t* end) const override;
-  bool Deserialize(const uint8_t** buf_ptr, const uint8_t* end) override;
+ size_t SerializedSize() const override;
+ uint8_t* Serialize(uint8_t* buf, const uint8_t* end) const override;
+ bool Deserialize(const uint8_t** buf_ptr, const uint8_t* end) override;
+
 private:
   TpmResourceManager& resource_manager_;
   std::function<TpmObjectSlot(TpmResourceManager&)> signing_key_fn_;
   uint32_t digest_size_;
-  keymaster::Serializable* wrapped_;
+  Serializable* wrapped_;
+  const Serializable* aad_;
+
+  std::optional<std::vector<uint8_t>> AppendAad(const uint8_t* sensitive,
+                                                size_t sensitive_size) const;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/in_process_tpm.cpp b/host/commands/secure_env/in_process_tpm.cpp
index f240c08..551283e 100644
--- a/host/commands/secure_env/in_process_tpm.cpp
+++ b/host/commands/secure_env/in_process_tpm.cpp
@@ -37,13 +37,18 @@
 
 #include <android-base/logging.h>
 
+#include <mutex>
+
+namespace cuttlefish {
+
 struct __attribute__((__packed__)) tpm_message_header {
   uint16_t tag;
   uint32_t length;
   uint32_t ordinal;
 };
 
-struct InProcessTpm::Impl {
+class InProcessTpm::Impl {
+ public:
   static Impl* FromContext(TSS2_TCTI_CONTEXT* context) {
     auto offset = offsetof(Impl, tcti_context_);
     char* context_char = reinterpret_cast<char*>(context);
@@ -94,63 +99,88 @@
     return TSS2_RC_SUCCESS;
   }
 
+  Impl() {
+    {
+      std::lock_guard<std::mutex> lock(global_mutex);
+      // This is a limitation of ms-tpm-20-ref
+      CHECK(!global_instance) << "InProcessTpm internally uses global data, so "
+                              << "only one can exist.";
+      global_instance = this;
+    }
+
+    tcti_context_.v1.magic = 0xFAD;
+    tcti_context_.v1.version = 1;
+    tcti_context_.v1.transmit = Impl::Transmit;
+    tcti_context_.v1.receive = Impl::Receive;
+    _plat__NVEnable(NULL);
+    if (_plat__NVNeedsManufacture()) {
+      // Can't use android logging here due to a macro conflict with TPM
+      // internals
+      LOG(DEBUG) << "Manufacturing TPM state";
+      if (TPM_Manufacture(1)) {
+        LOG(FATAL) << "Failed to manufacture TPM state";
+      }
+    }
+    _rpc__Signal_PowerOn(false);
+    _rpc__Signal_NvOn();
+
+    ESYS_CONTEXT* esys = nullptr;
+    auto rc = Esys_Initialize(&esys, TctiContext(), nullptr);
+    if (rc != TPM2_RC_SUCCESS) {
+      LOG(FATAL) << "Could not initialize esys: " << Tss2_RC_Decode(rc) << " ("
+                 << rc << ")";
+    }
+
+    rc = Esys_Startup(esys, TPM2_SU_CLEAR);
+    if (rc != TPM2_RC_SUCCESS) {
+      LOG(FATAL) << "TPM2_Startup failed: " << Tss2_RC_Decode(rc) << " (" << rc
+                 << ")";
+    }
+
+    TPM2B_AUTH auth = {};
+    Esys_TR_SetAuth(esys, ESYS_TR_RH_LOCKOUT, &auth);
+
+    rc = Esys_DictionaryAttackLockReset(
+        /* esysContext */ esys,
+        /* lockHandle */ ESYS_TR_RH_LOCKOUT,
+        /* shandle1 */ ESYS_TR_PASSWORD,
+        /* shandle2 */ ESYS_TR_NONE,
+        /* shandle3 */ ESYS_TR_NONE);
+
+    if (rc != TPM2_RC_SUCCESS) {
+      LOG(FATAL) << "Could not reset TPM lockout: " << Tss2_RC_Decode(rc)
+                 << " (" << rc << ")";
+    }
+
+    Esys_Finalize(&esys);
+  }
+
+  ~Impl() {
+    _rpc__Signal_NvOff();
+    _rpc__Signal_PowerOff();
+    std::lock_guard<std::mutex> lock(global_mutex);
+    global_instance = nullptr;
+  }
+
+  TSS2_TCTI_CONTEXT* TctiContext() {
+    return reinterpret_cast<TSS2_TCTI_CONTEXT*>(&tcti_context_);
+  }
+
+ private:
+  static std::mutex global_mutex;
+  static Impl* global_instance;
   TSS2_TCTI_CONTEXT_COMMON_CURRENT tcti_context_;
   std::list<std::vector<uint8_t>> command_queue_;
   std::mutex queue_mutex_;
 };
 
-InProcessTpm::InProcessTpm() : impl_(new Impl()) {
-  impl_->tcti_context_.v1.magic = 0xFAD;
-  impl_->tcti_context_.v1.version = 1;
-  impl_->tcti_context_.v1.transmit = Impl::Transmit;
-  impl_->tcti_context_.v1.receive = Impl::Receive;
-  _plat__NVEnable(NULL);
-  if (_plat__NVNeedsManufacture()) {
-    // Can't use android logging here due to a macro conflict with TPM internals
-    LOG(DEBUG) << "Manufacturing TPM state";
-    if (TPM_Manufacture(1)) {
-      LOG(FATAL) << "Failed to manufacture TPM state";
-    }
-  }
-  _rpc__Signal_PowerOn(false);
-  _rpc__Signal_NvOn();
+std::mutex InProcessTpm::Impl::global_mutex;
+InProcessTpm::Impl* InProcessTpm::Impl::global_instance;
 
-  ESYS_CONTEXT* esys = nullptr;
-  auto rc = Esys_Initialize(&esys, TctiContext(), nullptr);
-  if (rc != TPM2_RC_SUCCESS) {
-    LOG(FATAL) << "Could not initialize esys: " << Tss2_RC_Decode(rc)
-               << " (" << rc << ")";
-  }
+InProcessTpm::InProcessTpm() : impl_(new Impl()) {}
 
-  rc = Esys_Startup(esys, TPM2_SU_CLEAR);
-  if (rc != TPM2_RC_SUCCESS) {
-    LOG(FATAL) << "TPM2_Startup failed: " << Tss2_RC_Decode(rc)
-               << " (" << rc << ")";
-  }
+InProcessTpm::~InProcessTpm() = default;
 
-  TPM2B_AUTH auth = {};
-  Esys_TR_SetAuth(esys, ESYS_TR_RH_LOCKOUT, &auth);
+TSS2_TCTI_CONTEXT* InProcessTpm::TctiContext() { return impl_->TctiContext(); }
 
-  rc = Esys_DictionaryAttackLockReset(
-    /* esysContext */ esys,
-    /* lockHandle */ ESYS_TR_RH_LOCKOUT,
-    /* shandle1 */ ESYS_TR_PASSWORD,
-    /* shandle2 */ ESYS_TR_NONE,
-    /* shandle3 */ ESYS_TR_NONE);
-
-  if (rc != TPM2_RC_SUCCESS) {
-    LOG(FATAL) << "Could not reset TPM lockout: " << Tss2_RC_Decode(rc)
-               << " (" << rc << ")";
-  }
-
-  Esys_Finalize(&esys);
-}
-
-InProcessTpm::~InProcessTpm() {
-  _rpc__Signal_NvOff();
-  _rpc__Signal_PowerOff();
-}
-
-TSS2_TCTI_CONTEXT* InProcessTpm::TctiContext() {
-  return reinterpret_cast<TSS2_TCTI_CONTEXT*>(&impl_->tcti_context_);
-}
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/in_process_tpm.h b/host/commands/secure_env/in_process_tpm.h
index 35fecfb..624e7c4 100644
--- a/host/commands/secure_env/in_process_tpm.h
+++ b/host/commands/secure_env/in_process_tpm.h
@@ -23,6 +23,8 @@
 
 #include "host/commands/secure_env/tpm.h"
 
+namespace cuttlefish {
+
 /*
  * Exposes a TSS2_TCTI_CONTEXT for interacting with an in-process TPM simulator.
  *
@@ -41,7 +43,9 @@
 
   TSS2_TCTI_CONTEXT* TctiContext() override;
 private:
-  struct Impl;
+ class Impl;
 
-  std::unique_ptr<Impl> impl_;
+ std::unique_ptr<Impl> impl_;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/insecure_fallback_storage.cpp b/host/commands/secure_env/insecure_fallback_storage.cpp
index f906bd5..643a3ce 100644
--- a/host/commands/secure_env/insecure_fallback_storage.cpp
+++ b/host/commands/secure_env/insecure_fallback_storage.cpp
@@ -23,6 +23,8 @@
 #include "host/commands/secure_env/json_serializable.h"
 #include "host/commands/secure_env/tpm_random_source.h"
 
+namespace cuttlefish {
+
 static constexpr char kEntries[] = "entries";
 static constexpr char kKey[] = "key";
 static constexpr char kValue[] = "value";
@@ -147,3 +149,5 @@
   }
   return true;
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/insecure_fallback_storage.h b/host/commands/secure_env/insecure_fallback_storage.h
index 93788a0..64405be 100644
--- a/host/commands/secure_env/insecure_fallback_storage.h
+++ b/host/commands/secure_env/insecure_fallback_storage.h
@@ -23,6 +23,8 @@
 #include "host/commands/secure_env/gatekeeper_storage.h"
 #include "host/commands/secure_env/tpm_resource_manager.h"
 
+namespace cuttlefish {
+
 /**
  * A GatekeeperStorage fallback implementation that is less secure. It uses an
  * index file that is signed and encrypted by the TPM and the sensitive data
@@ -54,3 +56,5 @@
   std::string index_file_;
   Json::Value index_;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/json_serializable.cpp b/host/commands/secure_env/json_serializable.cpp
index d4f2fd8..630fb529 100644
--- a/host/commands/secure_env/json_serializable.cpp
+++ b/host/commands/secure_env/json_serializable.cpp
@@ -24,6 +24,8 @@
 #include "host/commands/secure_env/hmac_serializable.h"
 #include "host/commands/secure_env/primary_key_builder.h"
 
+namespace cuttlefish {
+
 static constexpr char kUniqueKey[] = "JsonSerializable";
 
 class JsonSerializable : public keymaster::Serializable {
@@ -89,8 +91,9 @@
   EncryptedSerializable encryption(
       resource_manager, parent_key_fn, sensitive_material);
   auto signing_key_fn = SigningKeyCreator(kUniqueKey);
-  HmacSerializable sign_check(
-      resource_manager, signing_key_fn, TPM2_SHA256_DIGEST_SIZE, &encryption);
+  HmacSerializable sign_check(resource_manager, signing_key_fn,
+                              TPM2_SHA256_DIGEST_SIZE, &encryption,
+                              /*aad=*/nullptr);
 
   auto size = sign_check.SerializedSize();
   LOG(INFO) << "size : " << size;
@@ -139,8 +142,9 @@
   EncryptedSerializable encryption(
       resource_manager, parent_key_fn, sensitive_material);
   auto signing_key_fn = SigningKeyCreator(kUniqueKey);
-  HmacSerializable sign_check(
-      resource_manager, signing_key_fn, TPM2_SHA256_DIGEST_SIZE, &encryption);
+  HmacSerializable sign_check(resource_manager, signing_key_fn,
+                              TPM2_SHA256_DIGEST_SIZE, &encryption,
+                              /*aad=*/nullptr);
 
   auto buf = reinterpret_cast<const uint8_t*>(buffer.data());
   auto buf_end = buf + buffer.size();
@@ -151,3 +155,5 @@
 
   return json;
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/json_serializable.h b/host/commands/secure_env/json_serializable.h
index f96aab3..984f396 100644
--- a/host/commands/secure_env/json_serializable.h
+++ b/host/commands/secure_env/json_serializable.h
@@ -19,7 +19,11 @@
 
 #include "host/commands/secure_env/tpm_resource_manager.h"
 
+namespace cuttlefish {
+
 bool WriteProtectedJsonToFile(
     TpmResourceManager&, const std::string& filename, Json::Value);
 Json::Value ReadProtectedJsonFromFile(
     TpmResourceManager&, const std::string& filename);
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/keymaster_responder.cpp b/host/commands/secure_env/keymaster_responder.cpp
index a564254..688ddf3 100644
--- a/host/commands/secure_env/keymaster_responder.cpp
+++ b/host/commands/secure_env/keymaster_responder.cpp
@@ -18,10 +18,11 @@
 #include <android-base/logging.h>
 #include <keymaster/android_keymaster_messages.h>
 
-KeymasterResponder::KeymasterResponder(
-    cuttlefish::KeymasterChannel& channel, keymaster::AndroidKeymaster& keymaster)
-    : channel_(channel), keymaster_(keymaster) {
-}
+namespace cuttlefish {
+
+KeymasterResponder::KeymasterResponder(cuttlefish::KeymasterChannel& channel,
+                                       keymaster::AndroidKeymaster& keymaster)
+    : channel_(channel), keymaster_(keymaster) {}
 
 bool KeymasterResponder::ProcessMessage() {
   auto request = channel_.ReceiveMessage();
@@ -31,19 +32,19 @@
   }
   const uint8_t* buffer = request->payload;
   const uint8_t* end = request->payload + request->payload_size;
-  switch(request->cmd) {
+  switch (request->cmd) {
     using namespace keymaster;
-#define HANDLE_MESSAGE(ENUM_NAME, METHOD_NAME) \
-    case ENUM_NAME: {\
-      METHOD_NAME##Request request(keymaster_.message_version()); \
-      if (!request.Deserialize(&buffer, end)) { \
-        LOG(ERROR) << "Failed to deserialize " #METHOD_NAME "Request"; \
-        return false; \
-      } \
-      METHOD_NAME##Response response(keymaster_.message_version()); \
-      keymaster_.METHOD_NAME(request, &response); \
-      return channel_.SendResponse(ENUM_NAME, response); \
-    }
+#define HANDLE_MESSAGE(ENUM_NAME, METHOD_NAME)                       \
+  case ENUM_NAME: {                                                  \
+    METHOD_NAME##Request request(keymaster_.message_version());      \
+    if (!request.Deserialize(&buffer, end)) {                        \
+      LOG(ERROR) << "Failed to deserialize " #METHOD_NAME "Request"; \
+      return false;                                                  \
+    }                                                                \
+    METHOD_NAME##Response response(keymaster_.message_version());    \
+    keymaster_.METHOD_NAME(request, &response);                      \
+    return channel_.SendResponse(ENUM_NAME, response);               \
+  }
     HANDLE_MESSAGE(GENERATE_KEY, GenerateKey)
     HANDLE_MESSAGE(BEGIN_OPERATION, BeginOperation)
     HANDLE_MESSAGE(UPDATE_OPERATION, UpdateOperation)
@@ -65,38 +66,48 @@
     HANDLE_MESSAGE(DELETE_KEY, DeleteKey)
     HANDLE_MESSAGE(DELETE_ALL_KEYS, DeleteAllKeys)
     HANDLE_MESSAGE(IMPORT_WRAPPED_KEY, ImportWrappedKey)
+    HANDLE_MESSAGE(GENERATE_RKP_KEY, GenerateRkpKey)
+    HANDLE_MESSAGE(GENERATE_CSR, GenerateCsr)
     HANDLE_MESSAGE(GENERATE_TIMESTAMP_TOKEN, GenerateTimestampToken)
 #undef HANDLE_MESSAGE
-#define HANDLE_MESSAGE_W_RETURN(ENUM_NAME, METHOD_NAME) \
-    case ENUM_NAME: {\
-    METHOD_NAME##Request request(keymaster_.message_version());     \
-      if (!request.Deserialize(&buffer, end)) { \
-        LOG(ERROR) << "Failed to deserialize " #METHOD_NAME "Request"; \
-        return false; \
-      } \
-      auto response = keymaster_.METHOD_NAME(request); \
-      return channel_.SendResponse(ENUM_NAME, response); \
-    }
+#define HANDLE_MESSAGE_W_RETURN(ENUM_NAME, METHOD_NAME)              \
+  case ENUM_NAME: {                                                  \
+    METHOD_NAME##Request request(keymaster_.message_version());      \
+    if (!request.Deserialize(&buffer, end)) {                        \
+      LOG(ERROR) << "Failed to deserialize " #METHOD_NAME "Request"; \
+      return false;                                                  \
+    }                                                                \
+    auto response = keymaster_.METHOD_NAME(request);                 \
+    return channel_.SendResponse(ENUM_NAME, response);               \
+  }
     HANDLE_MESSAGE_W_RETURN(COMPUTE_SHARED_HMAC, ComputeSharedHmac)
     HANDLE_MESSAGE_W_RETURN(VERIFY_AUTHORIZATION, VerifyAuthorization)
     HANDLE_MESSAGE_W_RETURN(DEVICE_LOCKED, DeviceLocked)
     HANDLE_MESSAGE_W_RETURN(GET_VERSION_2, GetVersion2)
-#undef HANDLE_MESSAGE
+    HANDLE_MESSAGE_W_RETURN(CONFIGURE_VENDOR_PATCHLEVEL,
+                            ConfigureVendorPatchlevel)
+    HANDLE_MESSAGE_W_RETURN(CONFIGURE_BOOT_PATCHLEVEL, ConfigureBootPatchlevel)
+    HANDLE_MESSAGE_W_RETURN(CONFIGURE_VERIFIED_BOOT_INFO,
+                            ConfigureVerifiedBootInfo)
+    HANDLE_MESSAGE_W_RETURN(GET_ROOT_OF_TRUST, GetRootOfTrust)
+#undef HANDLE_MESSAGE_W_RETURN
 #define HANDLE_MESSAGE_W_RETURN_NO_ARG(ENUM_NAME, METHOD_NAME) \
-    case ENUM_NAME: {\
-      auto response = keymaster_.METHOD_NAME(); \
-      return channel_.SendResponse(ENUM_NAME, response); \
-    }
-    HANDLE_MESSAGE_W_RETURN_NO_ARG(GET_HMAC_SHARING_PARAMETERS, GetHmacSharingParameters)
+  case ENUM_NAME: {                                            \
+    auto response = keymaster_.METHOD_NAME();                  \
+    return channel_.SendResponse(ENUM_NAME, response);         \
+  }
+    HANDLE_MESSAGE_W_RETURN_NO_ARG(GET_HMAC_SHARING_PARAMETERS,
+                                   GetHmacSharingParameters)
     HANDLE_MESSAGE_W_RETURN_NO_ARG(EARLY_BOOT_ENDED, EarlyBootEnded)
-#undef HANDLE_MESSAGE
+#undef HANDLE_MESSAGE_W_RETURN_NO_ARG
     case ADD_RNG_ENTROPY: {
       AddEntropyRequest request(keymaster_.message_version());
       if (!request.Deserialize(&buffer, end)) {
         LOG(ERROR) << "Failed to deserialize AddEntropyRequest";
         return false;
       }
-      AddEntropyResponse response(keymaster_.message_version());;
+      AddEntropyResponse response(keymaster_.message_version());
+      ;
       keymaster_.AddRngEntropy(request, &response);
       return channel_.SendResponse(ADD_RNG_ENTROPY, response);
     }
@@ -107,3 +118,5 @@
       return false;
   }
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/keymaster_responder.h b/host/commands/secure_env/keymaster_responder.h
index f8fb6ec..2bbd893 100644
--- a/host/commands/secure_env/keymaster_responder.h
+++ b/host/commands/secure_env/keymaster_responder.h
@@ -19,6 +19,8 @@
 
 #include "common/libs/security/keymaster_channel.h"
 
+namespace cuttlefish {
+
 class KeymasterResponder {
 private:
   cuttlefish::KeymasterChannel& channel_;
@@ -29,3 +31,5 @@
 
   bool ProcessMessage();
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/primary_key_builder.cpp b/host/commands/secure_env/primary_key_builder.cpp
index a44ff24..b333002 100644
--- a/host/commands/secure_env/primary_key_builder.cpp
+++ b/host/commands/secure_env/primary_key_builder.cpp
@@ -19,6 +19,8 @@
 #include <tss2/tss2_mu.h>
 #include <tss2/tss2_rc.h>
 
+namespace cuttlefish {
+
 PrimaryKeyBuilder::PrimaryKeyBuilder() : public_area_({}) {
   public_area_.nameAlg = TPM2_ALG_SHA256;
 };
@@ -133,3 +135,5 @@
     return key_builder.CreateKey(resource_manager);
   };
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/primary_key_builder.h b/host/commands/secure_env/primary_key_builder.h
index 46e5461..70170e7 100644
--- a/host/commands/secure_env/primary_key_builder.h
+++ b/host/commands/secure_env/primary_key_builder.h
@@ -22,6 +22,8 @@
 
 #include "host/commands/secure_env/tpm_resource_manager.h"
 
+namespace cuttlefish {
+
 class PrimaryKeyBuilder {
 public:
   PrimaryKeyBuilder();
@@ -40,3 +42,5 @@
 
 std::function<TpmObjectSlot(TpmResourceManager&)>
 ParentKeyCreator(const std::string& unique);
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/proxy_keymaster_context.h b/host/commands/secure_env/proxy_keymaster_context.h
new file mode 100644
index 0000000..e3bf426
--- /dev/null
+++ b/host/commands/secure_env/proxy_keymaster_context.h
@@ -0,0 +1,162 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <map>
+#include <vector>
+
+#include <keymaster/key.h>
+#include <keymaster/keymaster_context.h>
+#include <keymaster/km_openssl/attestation_record.h>
+
+#include "tpm_attestation_record.h"
+
+namespace cuttlefish {
+
+class TpmAttestationRecordContext;
+class TpmResourceManager;
+class TpmKeyBlobMaker;
+class TpmRandomSource;
+class TpmRemoteProvisioningContext;
+
+/**
+ * Implementation of KeymasterContext that proxies to another implementation.
+ *
+ * Because AndroidKeymaster wraps a KeymasterContext and puts it into a unique
+ * pointer, it doesn't let the implementor manage the lifetime of the
+ * KeymasterContext implementation. This proxy breaks that relationship, and
+ * allows the lifetimes to be distinct as long as the KeymasterContext instance
+ * outlives the AndroidKeymaster instance.
+ */
+class ProxyKeymasterContext : public keymaster::KeymasterContext {
+ public:
+  ProxyKeymasterContext(KeymasterContext& wrapped) : wrapped_(wrapped) {}
+  ~ProxyKeymasterContext() = default;
+
+  keymaster::KmVersion GetKmVersion() const override {
+    return wrapped_.GetKmVersion();
+  }
+
+  keymaster_error_t SetSystemVersion(uint32_t os_version,
+                                     uint32_t os_patchlevel) override {
+    return wrapped_.SetSystemVersion(os_version, os_patchlevel);
+  }
+  void GetSystemVersion(uint32_t* os_version,
+                        uint32_t* os_patchlevel) const override {
+    return wrapped_.GetSystemVersion(os_version, os_patchlevel);
+  }
+
+  const keymaster::KeyFactory* GetKeyFactory(
+      keymaster_algorithm_t algorithm) const override {
+    return wrapped_.GetKeyFactory(algorithm);
+  }
+  const keymaster::OperationFactory* GetOperationFactory(
+      keymaster_algorithm_t algorithm,
+      keymaster_purpose_t purpose) const override {
+    return wrapped_.GetOperationFactory(algorithm, purpose);
+  }
+  const keymaster_algorithm_t* GetSupportedAlgorithms(
+      size_t* algorithms_count) const override {
+    return wrapped_.GetSupportedAlgorithms(algorithms_count);
+  }
+
+  keymaster_error_t UpgradeKeyBlob(
+      const keymaster::KeymasterKeyBlob& key_to_upgrade,
+      const keymaster::AuthorizationSet& upgrade_params,
+      keymaster::KeymasterKeyBlob* upgraded_key) const override {
+    return wrapped_.UpgradeKeyBlob(key_to_upgrade, upgrade_params,
+                                   upgraded_key);
+  }
+
+  keymaster_error_t ParseKeyBlob(
+      const keymaster::KeymasterKeyBlob& blob,
+      const keymaster::AuthorizationSet& additional_params,
+      keymaster::UniquePtr<keymaster::Key>* key) const override {
+    return wrapped_.ParseKeyBlob(blob, additional_params, key);
+  }
+
+  keymaster_error_t AddRngEntropy(const uint8_t* buf,
+                                  size_t length) const override {
+    return wrapped_.AddRngEntropy(buf, length);
+  }
+
+  keymaster::KeymasterEnforcement* enforcement_policy() override {
+    return wrapped_.enforcement_policy();
+  }
+
+  keymaster::AttestationContext* attestation_context() override {
+    return wrapped_.attestation_context();
+  }
+
+  keymaster::CertificateChain GenerateAttestation(
+      const keymaster::Key& key,
+      const keymaster::AuthorizationSet& attest_params,
+      keymaster::UniquePtr<keymaster::Key> attest_key,
+      const keymaster::KeymasterBlob& issuer_subject,
+      keymaster_error_t* error) const override {
+    return wrapped_.GenerateAttestation(
+        key, attest_params, std::move(attest_key), issuer_subject, error);
+  }
+
+  keymaster::CertificateChain GenerateSelfSignedCertificate(
+      const keymaster::Key& key, const keymaster::AuthorizationSet& cert_params,
+      bool fake_signature, keymaster_error_t* error) const override {
+    return wrapped_.GenerateSelfSignedCertificate(key, cert_params,
+                                                  fake_signature, error);
+  }
+
+  keymaster_error_t UnwrapKey(
+      const keymaster::KeymasterKeyBlob& wrapped_key_blob,
+      const keymaster::KeymasterKeyBlob& wrapping_key_blob,
+      const keymaster::AuthorizationSet& wrapping_key_params,
+      const keymaster::KeymasterKeyBlob& masking_key,
+      keymaster::AuthorizationSet* wrapped_key_params,
+      keymaster_key_format_t* wrapped_key_format,
+      keymaster::KeymasterKeyBlob* wrapped_key_material) const override {
+    return wrapped_.UnwrapKey(
+        wrapped_key_blob, wrapping_key_blob, wrapping_key_params, masking_key,
+        wrapped_key_params, wrapped_key_format, wrapped_key_material);
+  }
+
+  keymaster::RemoteProvisioningContext* GetRemoteProvisioningContext()
+      const override {
+    return wrapped_.GetRemoteProvisioningContext();
+  }
+
+  keymaster_error_t SetVendorPatchlevel(uint32_t vendor_patchlevel) override {
+    return wrapped_.SetVendorPatchlevel(vendor_patchlevel);
+  }
+  keymaster_error_t SetBootPatchlevel(uint32_t boot_patchlevel) override {
+    return wrapped_.SetBootPatchlevel(boot_patchlevel);
+  }
+  keymaster_error_t SetVerifiedBootInfo(
+      std::string_view verified_boot_state, std::string_view bootloader_state,
+      const std::vector<uint8_t>& vbmeta_digest) {
+    return wrapped_.SetVerifiedBootInfo(verified_boot_state, bootloader_state,
+                                        vbmeta_digest);
+  }
+  std::optional<uint32_t> GetVendorPatchlevel() const override {
+    return wrapped_.GetVendorPatchlevel();
+  }
+  std::optional<uint32_t> GetBootPatchlevel() const override {
+    return wrapped_.GetBootPatchlevel();
+  }
+
+ private:
+  KeymasterContext& wrapped_;
+};
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/secure_env.cpp b/host/commands/secure_env/secure_env.cpp
index c050c58..97dd48c 100644
--- a/host/commands/secure_env/secure_env.cpp
+++ b/host/commands/secure_env/secure_env.cpp
@@ -16,22 +16,28 @@
 #include <thread>
 
 #include <android-base/logging.h>
+#include <fruit/fruit.h>
 #include <gflags/gflags.h>
 #include <keymaster/android_keymaster.h>
-#include <keymaster/soft_keymaster_logger.h>
 #include <keymaster/contexts/pure_soft_keymaster_context.h>
+#include <keymaster/soft_keymaster_logger.h>
 #include <tss2/tss2_esys.h>
 #include <tss2/tss2_rc.h>
 
 #include "common/libs/fs/shared_fd.h"
+#include "common/libs/security/confui_sign.h"
 #include "common/libs/security/gatekeeper_channel.h"
 #include "common/libs/security/keymaster_channel.h"
+#include "host/commands/kernel_log_monitor/kernel_log_server.h"
+#include "host/commands/kernel_log_monitor/utils.h"
+#include "host/commands/secure_env/confui_sign_server.h"
 #include "host/commands/secure_env/device_tpm.h"
 #include "host/commands/secure_env/fragile_tpm_storage.h"
 #include "host/commands/secure_env/gatekeeper_responder.h"
-#include "host/commands/secure_env/insecure_fallback_storage.h"
 #include "host/commands/secure_env/in_process_tpm.h"
+#include "host/commands/secure_env/insecure_fallback_storage.h"
 #include "host/commands/secure_env/keymaster_responder.h"
+#include "host/commands/secure_env/proxy_keymaster_context.h"
 #include "host/commands/secure_env/soft_gatekeeper.h"
 #include "host/commands/secure_env/tpm_gatekeeper.h"
 #include "host/commands/secure_env/tpm_keymaster_context.h"
@@ -39,13 +45,16 @@
 #include "host/commands/secure_env/tpm_resource_manager.h"
 #include "host/libs/config/logging.h"
 
-// Copied from AndroidKeymaster4Device
-constexpr size_t kOperationTableSize = 16;
-
+DEFINE_int32(confui_server_fd, -1, "A named socket to serve confirmation UI");
 DEFINE_int32(keymaster_fd_in, -1, "A pipe for keymaster communication");
 DEFINE_int32(keymaster_fd_out, -1, "A pipe for keymaster communication");
 DEFINE_int32(gatekeeper_fd_in, -1, "A pipe for gatekeeper communication");
 DEFINE_int32(gatekeeper_fd_out, -1, "A pipe for gatekeeper communication");
+DEFINE_int32(kernel_events_fd, -1,
+             "A pipe for monitoring events based on "
+             "messages written to the kernel log. This "
+             "is used by secure_env to monitor for "
+             "device reboots.");
 
 DEFINE_string(tpm_impl,
               "in_memory",
@@ -57,106 +66,175 @@
 DEFINE_string(gatekeeper_impl, "tpm",
               "The gatekeeper implementation. \"tpm\" or \"software\"");
 
-int main(int argc, char** argv) {
-  cuttlefish::DefaultSubprocessLogging(argv);
+namespace cuttlefish {
+namespace {
+
+// Copied from AndroidKeymaster4Device
+constexpr size_t kOperationTableSize = 16;
+
+// Dup a command line file descriptor into a SharedFD.
+SharedFD DupFdFlag(gflags::int32 fd) {
+  CHECK(fd != -1);
+  SharedFD duped = SharedFD::Dup(fd);
+  CHECK(duped->IsOpen()) << "Could not dup output fd: " << duped->StrError();
+  // The original FD is intentionally kept open so that we can re-exec this
+  // process without having to do a bunch of argv book-keeping.
+  return duped;
+}
+
+// Re-launch this process with all the same flags it was originallys started
+// with.
+[[noreturn]] void ReExecSelf() {
+  // Allocate +1 entry for terminating nullptr.
+  std::vector<char*> argv(gflags::GetArgvs().size() + 1, nullptr);
+  for (size_t i = 0; i < gflags::GetArgvs().size(); ++i) {
+    argv[i] = strdup(gflags::GetArgvs()[i].c_str());
+    CHECK(argv[i] != nullptr) << "OOM";
+  }
+  execv("/proc/self/exe", argv.data());
+  char buf[128];
+  LOG(FATAL) << "Exec failed, secure_env is out of sync with the guest: "
+             << errno << "(" << strerror_r(errno, buf, sizeof(buf)) << ")";
+  abort();  // LOG(FATAL) isn't marked as noreturn
+}
+
+// Spin up a thread that monitors for a kernel loaded event, then re-execs
+// this process. This way, secure_env's boot tracking matches up with the guest.
+std::thread StartKernelEventMonitor(SharedFD kernel_events_fd) {
+  return std::thread([kernel_events_fd]() {
+    while (kernel_events_fd->IsOpen()) {
+      auto read_result = monitor::ReadEvent(kernel_events_fd);
+      CHECK(read_result.has_value()) << kernel_events_fd->StrError();
+      if (read_result->event == monitor::Event::BootloaderLoaded) {
+        LOG(DEBUG) << "secure_env detected guest reboot, restarting.";
+        ReExecSelf();
+      }
+    }
+  });
+}
+
+fruit::Component<fruit::Required<gatekeeper::SoftGateKeeper, TpmGatekeeper,
+                                 TpmResourceManager>,
+                 gatekeeper::GateKeeper, keymaster::KeymasterEnforcement>
+ChooseGatekeeperComponent() {
+  if (FLAGS_gatekeeper_impl == "software") {
+    return fruit::createComponent()
+        .bind<gatekeeper::GateKeeper, gatekeeper::SoftGateKeeper>()
+        .registerProvider([]() -> keymaster::KeymasterEnforcement* {
+          return new keymaster::SoftKeymasterEnforcement(64, 64);
+        });
+  } else if (FLAGS_gatekeeper_impl == "tpm") {
+    return fruit::createComponent()
+        .bind<gatekeeper::GateKeeper, TpmGatekeeper>()
+        .registerProvider(
+            [](TpmResourceManager& resource_manager,
+               TpmGatekeeper& gatekeeper) -> keymaster::KeymasterEnforcement* {
+              return new TpmKeymasterEnforcement(resource_manager, gatekeeper);
+            });
+  } else {
+    LOG(FATAL) << "Invalid gatekeeper implementation: "
+               << FLAGS_gatekeeper_impl;
+    abort();
+  }
+}
+
+fruit::Component<TpmResourceManager, gatekeeper::GateKeeper,
+                 keymaster::KeymasterEnforcement>
+SecureEnvComponent() {
+  return fruit::createComponent()
+      .registerProvider([]() -> Tpm* {  // fruit will take ownership
+        if (FLAGS_tpm_impl == "in_memory") {
+          return new InProcessTpm();
+        } else if (FLAGS_tpm_impl == "host_device") {
+          return new DeviceTpm("/dev/tpm0");
+        } else {
+          LOG(FATAL) << "Unknown TPM implementation: " << FLAGS_tpm_impl;
+          abort();
+        }
+      })
+      .registerProvider([](Tpm* tpm) {
+        if (tpm->TctiContext() == nullptr) {
+          LOG(FATAL) << "Unable to connect to TPM implementation.";
+        }
+        ESYS_CONTEXT* esys_ptr = nullptr;
+        std::unique_ptr<ESYS_CONTEXT, void (*)(ESYS_CONTEXT*)> esys(
+            nullptr, [](ESYS_CONTEXT* esys) { Esys_Finalize(&esys); });
+        auto rc = Esys_Initialize(&esys_ptr, tpm->TctiContext(), nullptr);
+        if (rc != TPM2_RC_SUCCESS) {
+          LOG(FATAL) << "Could not initialize esys: " << Tss2_RC_Decode(rc)
+                     << " (" << rc << ")";
+        }
+        esys.reset(esys_ptr);
+        return esys;
+      })
+      .registerProvider(
+          [](std::unique_ptr<ESYS_CONTEXT, void (*)(ESYS_CONTEXT*)>& esys) {
+            return new TpmResourceManager(
+                esys.get());  // fruit will take ownership
+          })
+      .registerProvider([](TpmResourceManager& resource_manager) {
+        return new FragileTpmStorage(resource_manager, "gatekeeper_secure");
+      })
+      .registerProvider([](TpmResourceManager& resource_manager) {
+        return new InsecureFallbackStorage(resource_manager,
+                                           "gatekeeper_insecure");
+      })
+      .registerProvider([](TpmResourceManager& resource_manager,
+                           FragileTpmStorage& secure_storage,
+                           InsecureFallbackStorage& insecure_storage) {
+        return new TpmGatekeeper(resource_manager, secure_storage,
+                                 insecure_storage);
+      })
+      .registerProvider([]() { return new gatekeeper::SoftGateKeeper(); })
+      .install(ChooseGatekeeperComponent);
+}
+
+}  // namespace
+
+int SecureEnvMain(int argc, char** argv) {
+  DefaultSubprocessLogging(argv);
   gflags::ParseCommandLineFlags(&argc, &argv, true);
   keymaster::SoftKeymasterLogger km_logger;
 
-  std::unique_ptr<Tpm> tpm;
-  if (FLAGS_tpm_impl == "in_memory") {
-    tpm.reset(new InProcessTpm());
-  } else if (FLAGS_tpm_impl == "host_device") {
-    tpm.reset(new DeviceTpm("/dev/tpm0"));
-  } else {
-    LOG(FATAL) << "Unknown TPM implementation: " << FLAGS_tpm_impl;
-  }
+  fruit::Injector<TpmResourceManager, gatekeeper::GateKeeper,
+                  keymaster::KeymasterEnforcement>
+      injector(SecureEnvComponent);
+  TpmResourceManager* resource_manager = injector.get<TpmResourceManager*>();
+  gatekeeper::GateKeeper* gatekeeper = injector.get<gatekeeper::GateKeeper*>();
+  keymaster::KeymasterEnforcement* keymaster_enforcement =
+      injector.get<keymaster::KeymasterEnforcement*>();
 
-  if (tpm->TctiContext() == nullptr) {
-    LOG(FATAL) << "Unable to connect to TPM implementation.";
-  }
-
-  std::unique_ptr<TpmResourceManager> resource_manager;
-  std::unique_ptr<ESYS_CONTEXT, void(*)(ESYS_CONTEXT*)> esys(
-      nullptr, [](ESYS_CONTEXT* esys) { Esys_Finalize(&esys); });
-  if (FLAGS_keymint_impl == "tpm" || FLAGS_gatekeeper_impl == "tpm") {
-    ESYS_CONTEXT* esys_ptr = nullptr;
-    auto rc = Esys_Initialize(&esys_ptr, tpm->TctiContext(), nullptr);
-    if (rc != TPM2_RC_SUCCESS) {
-      LOG(FATAL) << "Could not initialize esys: " << Tss2_RC_Decode(rc)
-                 << " (" << rc << ")";
-    }
-    esys.reset(esys_ptr);
-    resource_manager.reset(new TpmResourceManager(esys.get()));
-  }
-
-  std::unique_ptr<GatekeeperStorage> secure_storage;
-  std::unique_ptr<GatekeeperStorage> insecure_storage;
-  std::unique_ptr<gatekeeper::GateKeeper> gatekeeper;
-  std::unique_ptr<keymaster::KeymasterEnforcement> keymaster_enforcement;
-  if (FLAGS_gatekeeper_impl == "software") {
-    gatekeeper.reset(new gatekeeper::SoftGateKeeper);
-    keymaster_enforcement.reset(
-        new keymaster::SoftKeymasterEnforcement(64, 64));
-  } else if (FLAGS_gatekeeper_impl == "tpm") {
-    secure_storage.reset(
-        new FragileTpmStorage(*resource_manager, "gatekeeper_secure"));
-    insecure_storage.reset(
-        new InsecureFallbackStorage(*resource_manager, "gatekeeper_insecure"));
-    TpmGatekeeper* tpm_gatekeeper =
-        new TpmGatekeeper(*resource_manager, *secure_storage, *insecure_storage);
-    gatekeeper.reset(tpm_gatekeeper);
-    keymaster_enforcement.reset(
-        new TpmKeymasterEnforcement(*resource_manager, *tpm_gatekeeper));
-  }
-
-  // keymaster::AndroidKeymaster puts the given pointer into a UniquePtr,
-  // taking ownership.
-  keymaster::KeymasterContext* keymaster_context;
+  std::unique_ptr<keymaster::KeymasterContext> keymaster_context;
   if (FLAGS_keymint_impl == "software") {
     // TODO: See if this is the right KM version.
-    keymaster_context =
-        new keymaster::PureSoftKeymasterContext(keymaster::KmVersion::KEYMASTER_4,
-                                                KM_SECURITY_LEVEL_SOFTWARE);
+    keymaster_context.reset(new keymaster::PureSoftKeymasterContext(
+        keymaster::KmVersion::KEYMINT_2, KM_SECURITY_LEVEL_SOFTWARE));
   } else if (FLAGS_keymint_impl == "tpm") {
-    keymaster_context =
-        new TpmKeymasterContext(*resource_manager, *keymaster_enforcement);
+    keymaster_context.reset(
+        new TpmKeymasterContext(*resource_manager, *keymaster_enforcement));
   } else {
     LOG(FATAL) << "Unknown keymaster implementation " << FLAGS_keymint_impl;
     return -1;
   }
+  // keymaster::AndroidKeymaster puts the context pointer into a UniquePtr,
+  // taking ownership.
   keymaster::AndroidKeymaster keymaster{
-      keymaster_context, kOperationTableSize,
-      keymaster::MessageVersion(keymaster::KmVersion::KEYMINT_1,
+      new ProxyKeymasterContext(*keymaster_context), kOperationTableSize,
+      keymaster::MessageVersion(keymaster::KmVersion::KEYMINT_2,
                                 0 /* km_date */)};
 
-  CHECK(FLAGS_keymaster_fd_in != -1);
-  auto keymaster_in = cuttlefish::SharedFD::Dup(FLAGS_keymaster_fd_in);
-  CHECK(keymaster_in->IsOpen()) << "Could not dup input fd: "
-                                << keymaster_in->StrError();
-  close(FLAGS_keymaster_fd_in);
+  auto confui_server_fd = DupFdFlag(FLAGS_confui_server_fd);
+  auto keymaster_in = DupFdFlag(FLAGS_keymaster_fd_in);
+  auto keymaster_out = DupFdFlag(FLAGS_keymaster_fd_out);
+  auto gatekeeper_in = DupFdFlag(FLAGS_gatekeeper_fd_in);
+  auto gatekeeper_out = DupFdFlag(FLAGS_gatekeeper_fd_out);
+  auto kernel_events_fd = DupFdFlag(FLAGS_kernel_events_fd);
 
-  CHECK(FLAGS_keymaster_fd_out != -1);
-  auto keymaster_out = cuttlefish::SharedFD::Dup(FLAGS_keymaster_fd_out);
-  CHECK(keymaster_out->IsOpen()) << "Could not dup output fd: "
-                                 << keymaster_out->StrError();
-  close(FLAGS_keymaster_fd_out);
+  std::vector<std::thread> threads;
 
-  CHECK(FLAGS_gatekeeper_fd_in != -1);
-  auto gatekeeper_in = cuttlefish::SharedFD::Dup(FLAGS_gatekeeper_fd_in);
-  CHECK(gatekeeper_in->IsOpen()) << "Could not dup input fd: "
-                                << gatekeeper_in->StrError();
-  close(FLAGS_gatekeeper_fd_in);
-
-  CHECK(FLAGS_gatekeeper_fd_out != -1);
-  auto gatekeeper_out = cuttlefish::SharedFD::Dup(FLAGS_gatekeeper_fd_out);
-  CHECK(gatekeeper_out->IsOpen()) << "Could not dup output fd: "
-                                  << keymaster_out->StrError();
-  close(FLAGS_gatekeeper_fd_out);
-
-  std::thread keymaster_thread([keymaster_in, keymaster_out, &keymaster]() {
+  threads.emplace_back([keymaster_in, keymaster_out, &keymaster]() {
     while (true) {
-      cuttlefish::KeymasterChannel keymaster_channel(
-          keymaster_in, keymaster_out);
+      KeymasterChannel keymaster_channel(keymaster_in, keymaster_out);
 
       KeymasterResponder keymaster_responder(keymaster_channel, keymaster);
 
@@ -165,10 +243,9 @@
     }
   });
 
-  std::thread gatekeeper_thread([gatekeeper_in, gatekeeper_out, &gatekeeper]() {
+  threads.emplace_back([gatekeeper_in, gatekeeper_out, &gatekeeper]() {
     while (true) {
-      cuttlefish::GatekeeperChannel gatekeeper_channel(
-          gatekeeper_in, gatekeeper_out);
+      GatekeeperChannel gatekeeper_channel(gatekeeper_in, gatekeeper_out);
 
       GatekeeperResponder gatekeeper_responder(gatekeeper_channel, *gatekeeper);
 
@@ -177,6 +254,22 @@
     }
   });
 
-  keymaster_thread.join();
-  gatekeeper_thread.join();
+  threads.emplace_back([confui_server_fd, resource_manager]() {
+    ConfUiSignServer confui_sign_server(*resource_manager, confui_server_fd);
+    // no return, infinite loop
+    confui_sign_server.MainLoop();
+  });
+  threads.emplace_back(StartKernelEventMonitor(kernel_events_fd));
+
+  for (auto& t : threads) {
+    t.join();
+  }
+
+  return 0;
+}
+
+}  // namespace cuttlefish
+
+int main(int argc, char** argv) {
+  return cuttlefish::SecureEnvMain(argc, argv);
 }
diff --git a/host/commands/secure_env/test_tpm.cpp b/host/commands/secure_env/test_tpm.cpp
new file mode 100644
index 0000000..14e43be
--- /dev/null
+++ b/host/commands/secure_env/test_tpm.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/secure_env/test_tpm.h"
+
+#include <android-base/logging.h>
+#include <tss2/tss2_rc.h>
+
+namespace cuttlefish {
+
+TestTpm::TestTpm() {
+  auto rc = Esys_Initialize(&esys_, tpm_.TctiContext(), nullptr);
+  if (rc != TPM2_RC_SUCCESS) {
+    LOG(FATAL) << "Could not initialize esys: " << Tss2_RC_Decode(rc) << " ("
+               << rc << ")";
+  }
+}
+
+TestTpm::~TestTpm() { Esys_Finalize(&esys_); }
+
+ESYS_CONTEXT* TestTpm::Esys() { return esys_; }
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/commands/secure_env/test_tpm.h
similarity index 69%
copy from common/libs/utils/size_utils.cpp
copy to host/commands/secure_env/test_tpm.h
index 9f25445..fb39680 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/commands/secure_env/test_tpm.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,22 @@
  * limitations under the License.
  */
 
-#include "common/libs/utils/size_utils.h"
+#include <tss2/tss2_esys.h>
 
-#include <unistd.h>
+#include "host/commands/secure_env/in_process_tpm.h"
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+class TestTpm {
+ public:
+  TestTpm();
+  ~TestTpm();
+
+  ESYS_CONTEXT* Esys();
+
+ private:
+  InProcessTpm tpm_;
+  ESYS_CONTEXT* esys_;
+};
 
 }  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm.h b/host/commands/secure_env/tpm.h
index 201e5e7..3175ca7 100644
--- a/host/commands/secure_env/tpm.h
+++ b/host/commands/secure_env/tpm.h
@@ -17,9 +17,13 @@
 
 #include <tss2/tss2_tcti.h>
 
+namespace cuttlefish {
+
 class Tpm {
 public:
   virtual ~Tpm() = default;
 
   virtual TSS2_TCTI_CONTEXT* TctiContext() = 0;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_attestation_record.cpp b/host/commands/secure_env/tpm_attestation_record.cpp
index 75a2920..674570b 100644
--- a/host/commands/secure_env/tpm_attestation_record.cpp
+++ b/host/commands/secure_env/tpm_attestation_record.cpp
@@ -22,10 +22,30 @@
 
 #include <android-base/logging.h>
 
+namespace cuttlefish {
+
+namespace {
+using VerifiedBootParams = keymaster::AttestationContext::VerifiedBootParams;
 using keymaster::AuthorizationSet;
 
+VerifiedBootParams MakeVbParams() {
+  VerifiedBootParams vb_params;
+
+  // TODO: If Cuttlefish ever supports a boot state other than "orange", we'll
+  // also need to plumb in the public key.
+  static uint8_t empty_vb_key[32] = {};
+  vb_params.verified_boot_key = {empty_vb_key, sizeof(empty_vb_key)};
+  vb_params.verified_boot_hash = {empty_vb_key, sizeof(empty_vb_key)};
+  vb_params.verified_boot_state = KM_VERIFIED_BOOT_UNVERIFIED;
+  vb_params.device_locked = false;
+  return vb_params;
+}
+
+}  // namespace
+
 TpmAttestationRecordContext::TpmAttestationRecordContext()
-    : keymaster::AttestationContext(::keymaster::KmVersion::KEYMINT_1),
+    : keymaster::AttestationContext(::keymaster::KmVersion::KEYMINT_2),
+      vb_params_(MakeVbParams()),
       unique_id_hbk_(16) {
   RAND_bytes(unique_id_hbk_.data(), unique_id_hbk_.size());
 }
@@ -35,15 +55,10 @@
 }
 
 keymaster_error_t TpmAttestationRecordContext::VerifyAndCopyDeviceIds(
-    const AuthorizationSet& attestation_params,
-    AuthorizationSet* attestation) const {
+    const AuthorizationSet& /*attestation_params*/,
+    AuthorizationSet* /*attestation*/) const {
   LOG(DEBUG) << "TODO(schuffelen): Implement VerifyAndCopyDeviceIds";
-  attestation->Difference(attestation_params);
-  attestation->Union(attestation_params);
-  if (int index = attestation->find(keymaster::TAG_ATTESTATION_APPLICATION_ID)) {
-    attestation->erase(index);
-  }
-  return KM_ERROR_OK;
+  return KM_ERROR_UNIMPLEMENTED;
 }
 
 keymaster::Buffer TpmAttestationRecordContext::GenerateUniqueId(
@@ -54,28 +69,10 @@
                                        application_id, reset_since_rotation);
 }
 
-const keymaster::AttestationContext::VerifiedBootParams*
-TpmAttestationRecordContext::GetVerifiedBootParams(keymaster_error_t* error) const {
-  LOG(DEBUG) << "TODO(schuffelen): Implement GetVerifiedBootParams";
-  if (!vb_params_) {
-      vb_params_.reset(new VerifiedBootParams{});
-
-      // TODO(schuffelen): Get this data out of vbmeta
-      static uint8_t fake_vb_key[32];
-      static bool fake_vb_key_initialized = false;
-      if (!fake_vb_key_initialized) {
-        for (int i = 0; i < sizeof(fake_vb_key); i++) {
-          fake_vb_key[i] = rand();
-        }
-        fake_vb_key_initialized = true;
-      }
-      vb_params_->verified_boot_key = {fake_vb_key, sizeof(fake_vb_key)};
-      vb_params_->verified_boot_hash = {fake_vb_key, sizeof(fake_vb_key)};
-      vb_params_->verified_boot_state = KM_VERIFIED_BOOT_VERIFIED;
-      vb_params_->device_locked = true;
-  }
+const VerifiedBootParams* TpmAttestationRecordContext::GetVerifiedBootParams(
+    keymaster_error_t* error) const {
   *error = KM_ERROR_OK;
-  return vb_params_.get();
+  return &vb_params_;
 }
 
 keymaster::KeymasterKeyBlob
@@ -89,3 +86,25 @@
                                                  keymaster_error_t* error) const {
   return keymaster::getAttestationChain(algorithm, error);
 }
+
+void TpmAttestationRecordContext::SetVerifiedBootInfo(
+    std::string_view verified_boot_state, std::string_view bootloader_state,
+    const std::vector<uint8_t>& vbmeta_digest) {
+  vbmeta_digest_ = vbmeta_digest;
+  vb_params_.verified_boot_hash = {vbmeta_digest_.data(),
+                                   vbmeta_digest_.size()};
+
+  if (verified_boot_state == "green") {
+    vb_params_.verified_boot_state = KM_VERIFIED_BOOT_VERIFIED;
+  } else if (verified_boot_state == "yellow") {
+    vb_params_.verified_boot_state = KM_VERIFIED_BOOT_SELF_SIGNED;
+  } else if (verified_boot_state == "red") {
+    vb_params_.verified_boot_state = KM_VERIFIED_BOOT_FAILED;
+  } else {  // Default to orange
+    vb_params_.verified_boot_state = KM_VERIFIED_BOOT_UNVERIFIED;
+  }
+
+  vb_params_.device_locked = bootloader_state == "locked";
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_attestation_record.h b/host/commands/secure_env/tpm_attestation_record.h
index 1609351..dba0e91 100644
--- a/host/commands/secure_env/tpm_attestation_record.h
+++ b/host/commands/secure_env/tpm_attestation_record.h
@@ -15,11 +15,17 @@
 
 #pragma once
 
+#include <cstdint>
 #include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
 #include <vector>
 
 #include <keymaster/attestation_context.h>
 
+namespace cuttlefish {
+
 class TpmAttestationRecordContext : public keymaster::AttestationContext {
 public:
  TpmAttestationRecordContext();
@@ -37,8 +43,14 @@
      keymaster_algorithm_t algorithm, keymaster_error_t* error) const override;
  keymaster::CertificateChain GetAttestationChain(
      keymaster_algorithm_t algorithm, keymaster_error_t* error) const override;
+ void SetVerifiedBootInfo(std::string_view verified_boot_state,
+                          std::string_view bootloader_state,
+                          const std::vector<uint8_t>& vbmeta_digest);
 
 private:
-    mutable std::unique_ptr<VerifiedBootParams> vb_params_;
-    std::vector<uint8_t> unique_id_hbk_;
+ std::vector<uint8_t> vbmeta_digest_;
+ VerifiedBootParams vb_params_;
+ std::vector<uint8_t> unique_id_hbk_;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_auth.cpp b/host/commands/secure_env/tpm_auth.cpp
index 10f4b2d..937dd5d 100644
--- a/host/commands/secure_env/tpm_auth.cpp
+++ b/host/commands/secure_env/tpm_auth.cpp
@@ -17,6 +17,8 @@
 
 #include <tuple>
 
+namespace cuttlefish {
+
 TpmAuth::TpmAuth(ESYS_TR auth): TpmAuth(auth, ESYS_TR_NONE, ESYS_TR_NONE) {}
 TpmAuth::TpmAuth(ESYS_TR auth1, ESYS_TR auth2)
     : TpmAuth(auth1, auth2, ESYS_TR_NONE) {}
@@ -41,3 +43,5 @@
 ESYS_TR TpmAuth::auth3() const {
   return auth3_;
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_auth.h b/host/commands/secure_env/tpm_auth.h
index 196b5fd..dec75c4 100644
--- a/host/commands/secure_env/tpm_auth.h
+++ b/host/commands/secure_env/tpm_auth.h
@@ -17,6 +17,8 @@
 
 #include <tss2/tss2_esys.h>
 
+namespace cuttlefish {
+
 /**
  * Authorization wrapper for TPM2 calls.
  *
@@ -41,3 +43,5 @@
   ESYS_TR auth2_;
   ESYS_TR auth3_;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_commands.cpp b/host/commands/secure_env/tpm_commands.cpp
index 78e9d7f..932202a 100644
--- a/host/commands/secure_env/tpm_commands.cpp
+++ b/host/commands/secure_env/tpm_commands.cpp
@@ -21,6 +21,8 @@
 #include <cstddef>
 #include <string>
 
+namespace cuttlefish {
+
 std::string TpmCommandName(std::uint32_t command_num) {
   switch(command_num) {
     #define MATCH_TPM_COMMAND(name) case name: return #name;
@@ -145,3 +147,5 @@
       return "Unknown";
   }
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_commands.h b/host/commands/secure_env/tpm_commands.h
index ee75844..421aa51 100644
--- a/host/commands/secure_env/tpm_commands.h
+++ b/host/commands/secure_env/tpm_commands.h
@@ -19,4 +19,8 @@
 #include <cstddef>
 #include <string>
 
+namespace cuttlefish {
+
 std::string TpmCommandName(std::uint32_t command_num);
+
+}
diff --git a/host/commands/secure_env/tpm_encrypt_decrypt.cpp b/host/commands/secure_env/tpm_encrypt_decrypt.cpp
index 4a1711e..c81d7d6 100644
--- a/host/commands/secure_env/tpm_encrypt_decrypt.cpp
+++ b/host/commands/secure_env/tpm_encrypt_decrypt.cpp
@@ -22,23 +22,23 @@
 #include <android-base/logging.h>
 #include <tss2/tss2_rc.h>
 
+namespace cuttlefish {
+
 using keymaster::KeymasterBlob;
 
-static bool TpmEncryptDecrypt(
-    ESYS_CONTEXT* esys,
-    ESYS_TR key_handle,
-    TpmAuth auth,
-    uint8_t* data_in,
-    uint8_t* data_out,
-    size_t data_size,
-    bool decrypt) {
+static bool TpmEncryptDecrypt(  //
+    ESYS_CONTEXT* esys, ESYS_TR key_handle, TpmAuth auth, const TPM2B_IV& iv,
+    uint8_t* data_in, uint8_t* data_out, size_t data_size, bool decrypt) {
+  if (iv.size != sizeof(iv.buffer)) {
+    LOG(ERROR) << "Input IV had wrong size: " << iv.size;
+    return false;
+  }
   // TODO(schuffelen): Pipeline this for performance. Will require reevaluating
   // the initialization vector logic.
   std::vector<unsigned char> converted(data_size);
   // malloc for parity with Esys_EncryptDecrypt2
   TPM2B_IV* init_vector_in = (TPM2B_IV*) malloc(sizeof(TPM2B_IV));
-  *init_vector_in = {};
-  init_vector_in->size = 16;
+  *init_vector_in = iv;
   for (auto processed = 0; processed < data_size;) {
     TPM2B_MAX_BUFFER in_data;
     in_data.size =
@@ -77,24 +77,18 @@
   return true;
 }
 
-bool TpmEncrypt(
-    ESYS_CONTEXT* esys,
-    ESYS_TR key_handle,
-    TpmAuth auth,
-    uint8_t* data_in,
-    uint8_t* data_out,
-    size_t data_size) {
-  return TpmEncryptDecrypt(
-      esys, key_handle, auth, data_in, data_out, data_size, false);
+bool TpmEncrypt(ESYS_CONTEXT* esys, ESYS_TR key_handle, TpmAuth auth,
+                const TPM2B_IV& iv, uint8_t* data_in, uint8_t* data_out,
+                size_t data_size) {
+  return TpmEncryptDecrypt(  //
+      esys, key_handle, auth, iv, data_in, data_out, data_size, false);
 }
 
-bool TpmDecrypt(
-    ESYS_CONTEXT* esys,
-    ESYS_TR key_handle,
-    TpmAuth auth,
-    uint8_t* data_in,
-    uint8_t* data_out,
-    size_t data_size) {
-  return TpmEncryptDecrypt(
-      esys, key_handle, auth, data_in, data_out, data_size, true);
+bool TpmDecrypt(ESYS_CONTEXT* esys, ESYS_TR key_handle, TpmAuth auth,
+                const TPM2B_IV& iv, uint8_t* data_in, uint8_t* data_out,
+                size_t data_size) {
+  return TpmEncryptDecrypt(  //
+      esys, key_handle, auth, iv, data_in, data_out, data_size, true);
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_encrypt_decrypt.h b/host/commands/secure_env/tpm_encrypt_decrypt.h
index b75bafb..a811cb7 100644
--- a/host/commands/secure_env/tpm_encrypt_decrypt.h
+++ b/host/commands/secure_env/tpm_encrypt_decrypt.h
@@ -20,20 +20,20 @@
 
 #include "host/commands/secure_env/tpm_auth.h"
 
+namespace cuttlefish {
+
 /**
  * Encrypt `data_in` to `data_out`, which are both buffers of size `data_size`.
  *
  * There are no integrity guarantees on this data: if the encrypted data is
  * corrupted, decrypting it could either fail or produce corrupted output.
+ *
+ * `iv` should be generated randomly, and can be stored unencrypted next to
+ * the plaintext.
  */
-bool TpmEncrypt(
-    ESYS_CONTEXT* esys,
-    ESYS_TR key_handle,
-    TpmAuth auth,
-    uint8_t* data_in,
-    uint8_t* data_out,
-    size_t data_size);
-
+bool TpmEncrypt(ESYS_CONTEXT* esys, ESYS_TR key_handle, TpmAuth auth,
+                const TPM2B_IV& iv, uint8_t* data_in, uint8_t* data_out,
+                size_t data_size);
 
 /**
  * Decrypt `data_in` to `data_out`, which are both buffers of size `data_size`.
@@ -41,10 +41,8 @@
  * There are no integrity guarantees on this data: if the encrypted data is
  * corrupted, decrypting it could either fail or produce corrupted output.
  */
-bool TpmDecrypt(
-    ESYS_CONTEXT* esys,
-    ESYS_TR key_handle,
-    TpmAuth auth,
-    uint8_t* data_in,
-    uint8_t* data_out,
-    size_t data_size);
+bool TpmDecrypt(ESYS_CONTEXT* esys, ESYS_TR key_handle, TpmAuth auth,
+                const TPM2B_IV& iv, uint8_t* data_in, uint8_t* data_out,
+                size_t data_size);
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_gatekeeper.cpp b/host/commands/secure_env/tpm_gatekeeper.cpp
index e4f61e7..c8497d5 100644
--- a/host/commands/secure_env/tpm_gatekeeper.cpp
+++ b/host/commands/secure_env/tpm_gatekeeper.cpp
@@ -29,6 +29,8 @@
 #include "host/commands/secure_env/tpm_hmac.h"
 #include "host/commands/secure_env/tpm_random_source.h"
 
+namespace cuttlefish {
+
 TpmGatekeeper::TpmGatekeeper(
     TpmResourceManager& resource_manager,
     GatekeeperStorage& secure_storage,
@@ -240,3 +242,4 @@
   return true;
 }
 
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_gatekeeper.h b/host/commands/secure_env/tpm_gatekeeper.h
index 021ab58..1c35bdc 100644
--- a/host/commands/secure_env/tpm_gatekeeper.h
+++ b/host/commands/secure_env/tpm_gatekeeper.h
@@ -21,6 +21,8 @@
 #include "host/commands/secure_env/gatekeeper_storage.h"
 #include "host/commands/secure_env/tpm_resource_manager.h"
 
+namespace cuttlefish {
+
 /**
  * See method descriptions for this class in
  * system/gatekeeper/include/gatekeeper/gatekeeper.h
@@ -81,3 +83,5 @@
   GatekeeperStorage& secure_storage_;
   GatekeeperStorage& insecure_storage_;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_hmac.cpp b/host/commands/secure_env/tpm_hmac.cpp
index 80003d0..b566659 100644
--- a/host/commands/secure_env/tpm_hmac.cpp
+++ b/host/commands/secure_env/tpm_hmac.cpp
@@ -20,6 +20,8 @@
 
 #include "host/commands/secure_env/tpm_resource_manager.h"
 
+namespace cuttlefish {
+
 /* For data large enough to fit in a single TPM2_HMAC call. */
 static UniqueEsysPtr<TPM2B_DIGEST> OneshotHmac(
     TpmResourceManager& resource_manager,
@@ -153,3 +155,5 @@
   auto fn = data_size > TPM2_MAX_DIGEST_BUFFER ? SegmentedHmac : OneshotHmac;
   return fn(resource_manager, key_handle, auth, data, data_size);
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_hmac.h b/host/commands/secure_env/tpm_hmac.h
index e4686b6..09aa011 100644
--- a/host/commands/secure_env/tpm_hmac.h
+++ b/host/commands/secure_env/tpm_hmac.h
@@ -21,6 +21,8 @@
 
 #include "host/commands/secure_env/tpm_auth.h"
 
+namespace cuttlefish {
+
 class TpmResourceManager;
 
 struct EsysDeleter {
@@ -49,3 +51,5 @@
     TpmAuth auth,
     const uint8_t* data,
     size_t data_size);
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_key_blob_maker.cpp b/host/commands/secure_env/tpm_key_blob_maker.cpp
index 64f344a..38236d8 100644
--- a/host/commands/secure_env/tpm_key_blob_maker.cpp
+++ b/host/commands/secure_env/tpm_key_blob_maker.cpp
@@ -26,6 +26,8 @@
 #include "host/commands/secure_env/hmac_serializable.h"
 #include "host/commands/secure_env/primary_key_builder.h"
 
+namespace cuttlefish {
+
 using keymaster::AuthorizationSet;
 using keymaster::KeymasterKeyBlob;
 using keymaster::Serializable;
@@ -41,9 +43,76 @@
 static keymaster_error_t SplitEnforcedProperties(
     const keymaster::AuthorizationSet& key_description,
     keymaster::AuthorizationSet* hw_enforced,
-    keymaster::AuthorizationSet* sw_enforced) {
+    keymaster::AuthorizationSet* sw_enforced,
+    keymaster::AuthorizationSet* hidden) {
   for (auto& entry : key_description) {
     switch (entry.tag) {
+      // These cannot be specified by the client.
+      case KM_TAG_BOOT_PATCHLEVEL:
+      case KM_TAG_ORIGIN:
+      case KM_TAG_OS_PATCHLEVEL:
+      case KM_TAG_OS_VERSION:
+      case KM_TAG_ROOT_OF_TRUST:
+      case KM_TAG_VENDOR_PATCHLEVEL:
+        LOG(DEBUG) << "Root of trust and origin tags may not be specified";
+        return KM_ERROR_INVALID_TAG;
+
+      // These are hidden
+      case KM_TAG_APPLICATION_DATA:
+      case KM_TAG_APPLICATION_ID:
+        hidden->push_back(entry);
+        break;
+
+      // These should not be in key descriptions because they're for operation
+      // parameters.
+      case KM_TAG_ASSOCIATED_DATA:
+      case KM_TAG_AUTH_TOKEN:
+      case KM_TAG_CONFIRMATION_TOKEN:
+      case KM_TAG_INVALID:
+      case KM_TAG_MAC_LENGTH:
+      case KM_TAG_NONCE:
+        LOG(DEBUG) << "Tag " << entry.tag
+                   << " not allowed in key generation/import";
+        break;
+
+      // These are provided to support attestation key generation, but should
+      // not be included in the key characteristics.
+      case KM_TAG_ATTESTATION_APPLICATION_ID:
+      case KM_TAG_ATTESTATION_CHALLENGE:
+      case KM_TAG_ATTESTATION_ID_BRAND:
+      case KM_TAG_ATTESTATION_ID_DEVICE:
+      case KM_TAG_ATTESTATION_ID_IMEI:
+      case KM_TAG_ATTESTATION_ID_MANUFACTURER:
+      case KM_TAG_ATTESTATION_ID_MEID:
+      case KM_TAG_ATTESTATION_ID_MODEL:
+      case KM_TAG_ATTESTATION_ID_PRODUCT:
+      case KM_TAG_ATTESTATION_ID_SERIAL:
+      case KM_TAG_CERTIFICATE_SERIAL:
+      case KM_TAG_CERTIFICATE_SUBJECT:
+      case KM_TAG_CERTIFICATE_NOT_BEFORE:
+      case KM_TAG_CERTIFICATE_NOT_AFTER:
+      case KM_TAG_RESET_SINCE_ID_ROTATION:
+        break;
+
+      // strongbox-only tags
+      case KM_TAG_DEVICE_UNIQUE_ATTESTATION:
+        LOG(DEBUG) << "Strongbox-only tag: " << entry.tag;
+        return KM_ERROR_UNSUPPORTED_TAG;
+
+      case KM_TAG_ROLLBACK_RESISTANT:
+        return KM_ERROR_UNSUPPORTED_TAG;
+
+      case KM_TAG_ROLLBACK_RESISTANCE:
+        LOG(DEBUG) << "Rollback resistance is not implemented.";
+        return KM_ERROR_ROLLBACK_RESISTANCE_UNAVAILABLE;
+
+      // These are nominally HW tags, but we don't actually support HW key
+      // attestation yet.
+      case KM_TAG_ALLOW_WHILE_ON_BODY:
+      case KM_TAG_EXPORTABLE:
+      case KM_TAG_IDENTITY_CREDENTIAL_KEY:
+      case KM_TAG_STORAGE_KEY:
+
       case KM_TAG_PURPOSE:
       case KM_TAG_ALGORITHM:
       case KM_TAG_KEY_SIZE:
@@ -63,17 +132,32 @@
       case KM_TAG_EC_CURVE:
       case KM_TAG_ECIES_SINGLE_HASH_MODE:
       case KM_TAG_USER_AUTH_TYPE:
-      case KM_TAG_ORIGIN:
-      case KM_TAG_OS_VERSION:
-      case KM_TAG_OS_PATCHLEVEL:
       case KM_TAG_EARLY_BOOT_ONLY:
       case KM_TAG_UNLOCKED_DEVICE_REQUIRED:
         hw_enforced->push_back(entry);
         break;
-      default:
+
+      // The remaining tags are all software.
+      case KM_TAG_ACTIVE_DATETIME:
+      case KM_TAG_ALL_APPLICATIONS:
+      case KM_TAG_ALL_USERS:
+      case KM_TAG_BOOTLOADER_ONLY:
+      case KM_TAG_CREATION_DATETIME:
+      case KM_TAG_INCLUDE_UNIQUE_ID:
+      case KM_TAG_MAX_BOOT_LEVEL:
+      case KM_TAG_ORIGINATION_EXPIRE_DATETIME:
+      case KM_TAG_RSA_OAEP_MGF_DIGEST:
+      case KM_TAG_TRUSTED_CONFIRMATION_REQUIRED:
+      case KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED:
+      case KM_TAG_UNIQUE_ID:
+      case KM_TAG_USAGE_COUNT_LIMIT:
+      case KM_TAG_USAGE_EXPIRE_DATETIME:
+      case KM_TAG_USER_ID:
         sw_enforced->push_back(entry);
+        break;
     }
   }
+
   return KM_ERROR_OK;
 }
 
@@ -102,20 +186,9 @@
     KeymasterKeyBlob* blob,
     AuthorizationSet* hw_enforced,
     AuthorizationSet* sw_enforced) const {
-  std::set<keymaster_tag_t> protected_tags = {
-    KM_TAG_ROOT_OF_TRUST,
-    KM_TAG_ORIGIN,
-    KM_TAG_OS_VERSION,
-    KM_TAG_OS_PATCHLEVEL,
-  };
-  for (auto tag : protected_tags) {
-    if (key_description.Contains(tag)) {
-      LOG(ERROR) << "Invalid tag " << tag;
-      return KM_ERROR_INVALID_TAG;
-    }
-  }
-  auto rc =
-      SplitEnforcedProperties(key_description, hw_enforced, sw_enforced);
+  AuthorizationSet hidden;
+  auto rc = SplitEnforcedProperties(key_description, hw_enforced, sw_enforced,
+                                    &hidden);
   if (rc != KM_ERROR_OK) {
     return rc;
   }
@@ -125,13 +198,22 @@
   hw_enforced->push_back(keymaster::TAG_OS_VERSION, os_version_);
   hw_enforced->push_back(keymaster::TAG_OS_PATCHLEVEL, os_patchlevel_);
 
+  if (vendor_patchlevel_) {
+    hw_enforced->push_back(keymaster::TAG_VENDOR_PATCHLEVEL,
+                           *vendor_patchlevel_);
+  }
+  if (boot_patchlevel_) {
+    hw_enforced->push_back(keymaster::TAG_BOOT_PATCHLEVEL, *boot_patchlevel_);
+  }
+
   return UnvalidatedCreateKeyBlob(key_material, *hw_enforced, *sw_enforced,
-                                  blob);
+                                  hidden, blob);
 }
 
 keymaster_error_t TpmKeyBlobMaker::UnvalidatedCreateKeyBlob(
     const KeymasterKeyBlob& key_material, const AuthorizationSet& hw_enforced,
-    const AuthorizationSet& sw_enforced, KeymasterKeyBlob* blob) const {
+    const AuthorizationSet& sw_enforced, const AuthorizationSet& hidden,
+    KeymasterKeyBlob* blob) const {
   keymaster::Buffer key_material_buffer(
       key_material.key_material, key_material.key_material_size);
   AuthorizationSet hw_enforced_mutable = hw_enforced;
@@ -142,8 +224,12 @@
   EncryptedSerializable encryption(
       resource_manager_, parent_key_fn, sensitive_material);
   auto signing_key_fn = SigningKeyCreator(kUniqueKey);
-  HmacSerializable sign_check(
-      resource_manager_, signing_key_fn, TPM2_SHA256_DIGEST_SIZE, &encryption);
+  // TODO(b/154956668) The "hidden" tags should also be mixed into the TPM ACL
+  // so that the TPM requires them to be presented to unwrap the key. This is
+  // necessary to meet the requirement that full breach of KeyMint means an
+  // attacker cannot unwrap keys w/o the application id/data.
+  HmacSerializable sign_check(resource_manager_, signing_key_fn,
+                              TPM2_SHA256_DIGEST_SIZE, &encryption, &hidden);
   auto generated_blob = SerializableToKeyBlob(sign_check);
   LOG(VERBOSE) << "Keymaster key size: " << generated_blob.key_material_size;
   if (generated_blob.key_material_size != 0) {
@@ -155,9 +241,8 @@
 }
 
 keymaster_error_t TpmKeyBlobMaker::UnwrapKeyBlob(
-    const keymaster_key_blob_t& blob,
-    AuthorizationSet* hw_enforced,
-    AuthorizationSet* sw_enforced,
+    const keymaster_key_blob_t& blob, AuthorizationSet* hw_enforced,
+    AuthorizationSet* sw_enforced, const AuthorizationSet& hidden,
     KeymasterKeyBlob* key_material) const {
   keymaster::Buffer key_material_buffer(blob.key_material_size);
   CompositeSerializable sensitive_material(
@@ -166,17 +251,17 @@
   EncryptedSerializable encryption(
       resource_manager_, parent_key_fn, sensitive_material);
   auto signing_key_fn = SigningKeyCreator(kUniqueKey);
-  HmacSerializable sign_check(
-      resource_manager_, signing_key_fn, TPM2_SHA256_DIGEST_SIZE, &encryption);
+  HmacSerializable sign_check(resource_manager_, signing_key_fn,
+                              TPM2_SHA256_DIGEST_SIZE, &encryption, &hidden);
   auto buf = blob.key_material;
   auto buf_end = buf + blob.key_material_size;
   if (!sign_check.Deserialize(&buf, buf_end)) {
     LOG(ERROR) << "Failed to deserialize key.";
-    return KM_ERROR_UNKNOWN_ERROR;
+    return KM_ERROR_INVALID_KEY_BLOB;
   }
   if (key_material_buffer.available_read() == 0) {
     LOG(ERROR) << "Key material was corrupted and the size was too large";
-    return KM_ERROR_UNKNOWN_ERROR;
+    return KM_ERROR_INVALID_KEY_BLOB;
   }
   *key_material = KeymasterKeyBlob(
       key_material_buffer.peek_read(), key_material_buffer.available_read());
@@ -185,8 +270,22 @@
 
 keymaster_error_t TpmKeyBlobMaker::SetSystemVersion(
     uint32_t os_version, uint32_t os_patchlevel) {
-  // TODO(b/155697375): Only accept new values of these from the bootloader
+  // TODO(b/201561154): Only accept new values of these from the bootloader
   os_version_ = os_version;
   os_patchlevel_ = os_patchlevel;
   return KM_ERROR_OK;
 }
+
+keymaster_error_t TpmKeyBlobMaker::SetVendorPatchlevel(uint32_t patchlevel) {
+  // TODO(b/201561154): Only accept new values of these from the bootloader
+  vendor_patchlevel_ = patchlevel;
+  return KM_ERROR_OK;
+}
+
+keymaster_error_t TpmKeyBlobMaker::SetBootPatchlevel(uint32_t boot_patchlevel) {
+  // TODO(b/201561154): Only accept new values of these from the bootloader
+  boot_patchlevel_ = boot_patchlevel;
+  return KM_ERROR_OK;
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_key_blob_maker.h b/host/commands/secure_env/tpm_key_blob_maker.h
index a0483b4..b76d1dc 100644
--- a/host/commands/secure_env/tpm_key_blob_maker.h
+++ b/host/commands/secure_env/tpm_key_blob_maker.h
@@ -15,10 +15,14 @@
 
 #pragma once
 
+#include <optional>
+//
 #include <keymaster/soft_key_factory.h>
-
+//
 #include "host/commands/secure_env/tpm_resource_manager.h"
 
+namespace cuttlefish {
+
 /**
  * Encrypts key data using a TPM-resident key and signs it with a TPM-resident
  * key for privacy and integrity.
@@ -43,6 +47,7 @@
       const keymaster::KeymasterKeyBlob& key_material,
       const keymaster::AuthorizationSet& hw_enforced,
       const keymaster::AuthorizationSet& sw_enforced,
+      const keymaster::AuthorizationSet& hidden,
       keymaster::KeymasterKeyBlob* blob) const;
 
   /**
@@ -60,11 +65,19 @@
       const keymaster_key_blob_t& blob,
       keymaster::AuthorizationSet* hw_enforced,
       keymaster::AuthorizationSet* sw_enforced,
+      const keymaster::AuthorizationSet& hidden,
       keymaster::KeymasterKeyBlob* key_material) const;
 
   keymaster_error_t SetSystemVersion(uint32_t os_version, uint32_t os_patchlevel);
-private:
+  keymaster_error_t SetVendorPatchlevel(uint32_t vendor_patchlevel);
+  keymaster_error_t SetBootPatchlevel(uint32_t boot_patchlevel);
+
+ private:
   TpmResourceManager& resource_manager_;
   uint32_t os_version_;
   uint32_t os_patchlevel_;
+  std::optional<uint32_t> vendor_patchlevel_;
+  std::optional<uint32_t> boot_patchlevel_;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_keymaster_context.cpp b/host/commands/secure_env/tpm_keymaster_context.cpp
index 850d48b..4a60915 100644
--- a/host/commands/secure_env/tpm_keymaster_context.cpp
+++ b/host/commands/secure_env/tpm_keymaster_context.cpp
@@ -26,28 +26,66 @@
 #include <keymaster/km_openssl/rsa_key_factory.h>
 #include <keymaster/km_openssl/soft_keymaster_enforcement.h>
 #include <keymaster/km_openssl/triple_des_key.h>
+#include <keymaster/operation.h>
+#include <keymaster/wrapped_key.h>
 
 #include "host/commands/secure_env/tpm_attestation_record.h"
-#include "host/commands/secure_env/tpm_random_source.h"
 #include "host/commands/secure_env/tpm_key_blob_maker.h"
+#include "host/commands/secure_env/tpm_random_source.h"
+#include "host/commands/secure_env/tpm_remote_provisioning_context.h"
 
+namespace cuttlefish {
+
+namespace {
 using keymaster::AuthorizationSet;
-using keymaster::KeymasterKeyBlob;
 using keymaster::KeyFactory;
+using keymaster::KeymasterBlob;
+using keymaster::KeymasterKeyBlob;
 using keymaster::OperationFactory;
 
+keymaster::AuthorizationSet GetHiddenTags(
+    const AuthorizationSet& authorizations) {
+  keymaster::AuthorizationSet output;
+  keymaster_blob_t entry;
+  if (authorizations.GetTagValue(keymaster::TAG_APPLICATION_ID, &entry)) {
+    output.push_back(keymaster::TAG_APPLICATION_ID, entry.data,
+                     entry.data_length);
+  }
+  if (authorizations.GetTagValue(keymaster::TAG_APPLICATION_DATA, &entry)) {
+    output.push_back(keymaster::TAG_APPLICATION_DATA, entry.data,
+                     entry.data_length);
+  }
+  return output;
+}
+
+keymaster_error_t TranslateAuthorizationSetError(AuthorizationSet::Error err) {
+  switch (err) {
+    case AuthorizationSet::OK:
+      return KM_ERROR_OK;
+    case AuthorizationSet::ALLOCATION_FAILURE:
+      return KM_ERROR_MEMORY_ALLOCATION_FAILED;
+    case AuthorizationSet::MALFORMED_DATA:
+      return KM_ERROR_UNKNOWN_ERROR;
+  }
+  return KM_ERROR_UNKNOWN_ERROR;
+}
+
+}  // namespace
+
 TpmKeymasterContext::TpmKeymasterContext(
     TpmResourceManager& resource_manager,
     keymaster::KeymasterEnforcement& enforcement)
-    : resource_manager_(resource_manager)
-    , enforcement_(enforcement)
-    , key_blob_maker_(new TpmKeyBlobMaker(resource_manager_))
-    , random_source_(new TpmRandomSource(resource_manager_.Esys()))
-    , attestation_context_(new TpmAttestationRecordContext()) {
-  key_factories_.emplace(
-      KM_ALGORITHM_RSA, new keymaster::RsaKeyFactory(*key_blob_maker_, *this));
-  key_factories_.emplace(
-      KM_ALGORITHM_EC, new keymaster::EcKeyFactory(*key_blob_maker_, *this));
+    : resource_manager_(resource_manager),
+      enforcement_(enforcement),
+      key_blob_maker_(new TpmKeyBlobMaker(resource_manager_)),
+      random_source_(new TpmRandomSource(resource_manager_.Esys())),
+      attestation_context_(new TpmAttestationRecordContext),
+      remote_provisioning_context_(
+          new TpmRemoteProvisioningContext(resource_manager_)) {
+  key_factories_.emplace(KM_ALGORITHM_RSA,
+                         new keymaster::RsaKeyFactory(*key_blob_maker_, *this));
+  key_factories_.emplace(KM_ALGORITHM_EC,
+                         new keymaster::EcKeyFactory(*key_blob_maker_, *this));
   key_factories_.emplace(
       KM_ALGORITHM_AES,
       new keymaster::AesKeyFactory(*key_blob_maker_, *random_source_));
@@ -68,11 +106,12 @@
   os_version_ = os_version;
   os_patchlevel_ = os_patchlevel;
   key_blob_maker_->SetSystemVersion(os_version, os_patchlevel);
+  remote_provisioning_context_->SetSystemVersion(os_version_, os_patchlevel_);
   return KM_ERROR_OK;
 }
 
-void TpmKeymasterContext::GetSystemVersion(
-    uint32_t* os_version, uint32_t* os_patchlevel) const {
+void TpmKeymasterContext::GetSystemVersion(uint32_t* os_version,
+                                           uint32_t* os_patchlevel) const {
   *os_version = os_version_;
   *os_patchlevel = os_patchlevel_;
 }
@@ -87,12 +126,12 @@
   return it->second.get();
 }
 
-const OperationFactory* TpmKeymasterContext::GetOperationFactory(
+OperationFactory* TpmKeymasterContext::GetOperationFactory(
     keymaster_algorithm_t algorithm, keymaster_purpose_t purpose) const {
   auto key_factory = GetKeyFactory(algorithm);
   if (key_factory == nullptr) {
     LOG(ERROR) << "Tried to get operation factory for " << purpose
-              << " for invalid algorithm " << algorithm;
+               << " for invalid algorithm " << algorithm;
     return nullptr;
   }
   auto operation_factory = key_factory->GetOperationFactory(purpose);
@@ -104,18 +143,16 @@
 }
 
 const keymaster_algorithm_t* TpmKeymasterContext::GetSupportedAlgorithms(
-      size_t* algorithms_count) const {
+    size_t* algorithms_count) const {
   *algorithms_count = supported_algorithms_.size();
   return supported_algorithms_.data();
 }
 
-// Based on https://cs.android.com/android/platform/superproject/+/master:system/keymaster/key_blob_utils/software_keyblobs.cpp;l=44;drc=master
+// Based on
+// https://cs.android.com/android/platform/superproject/+/master:system/keymaster/key_blob_utils/software_keyblobs.cpp;l=44;drc=master
 
-static bool UpgradeIntegerTag(
-    keymaster_tag_t tag,
-    uint32_t value,
-    AuthorizationSet* set,
-    bool* set_changed) {
+static bool UpgradeIntegerTag(keymaster_tag_t tag, uint32_t value,
+                              AuthorizationSet* set, bool* set_changed) {
   int index = set->find(tag);
   if (index == -1) {
     keymaster_key_param_t param;
@@ -137,7 +174,8 @@
   return true;
 }
 
-// Based on https://cs.android.com/android/platform/superproject/+/master:system/keymaster/key_blob_utils/software_keyblobs.cpp;l=310;drc=master
+// Based on
+// https://cs.android.com/android/platform/superproject/+/master:system/keymaster/key_blob_utils/software_keyblobs.cpp;l=310;drc=master
 
 keymaster_error_t TpmKeymasterContext::UpgradeKeyBlob(
     const KeymasterKeyBlob& blob_to_upgrade,
@@ -187,23 +225,20 @@
 
   return key_blob_maker_->UnvalidatedCreateKeyBlob(
       key->key_material(), key->hw_enforced(), key->sw_enforced(),
-      upgraded_key);
+      GetHiddenTags(upgrade_params), upgraded_key);
 }
 
 keymaster_error_t TpmKeymasterContext::ParseKeyBlob(
-    const KeymasterKeyBlob& blob,
-    const AuthorizationSet& additional_params,
+    const KeymasterKeyBlob& blob, const AuthorizationSet& additional_params,
     keymaster::UniquePtr<keymaster::Key>* key) const {
   keymaster::AuthorizationSet hw_enforced;
   keymaster::AuthorizationSet sw_enforced;
   keymaster::KeymasterKeyBlob key_material;
 
-  auto rc =
-      key_blob_maker_->UnwrapKeyBlob(
-          blob,
-          &hw_enforced,
-          &sw_enforced,
-          &key_material);
+  keymaster::AuthorizationSet hidden = GetHiddenTags(additional_params);
+
+  auto rc = key_blob_maker_->UnwrapKeyBlob(blob, &hw_enforced, &sw_enforced,
+                                           hidden, &key_material);
   if (rc != KM_ERROR_OK) {
     LOG(ERROR) << "Failed to unwrap key: " << rc;
     return rc;
@@ -221,21 +256,16 @@
     LOG(ERROR) << "Unable to find key factory for " << algorithm;
     return KM_ERROR_UNSUPPORTED_ALGORITHM;
   }
-  rc =
-      factory->LoadKey(
-          std::move(key_material),
-          additional_params,
-          std::move(hw_enforced),
-          std::move(sw_enforced),
-          key);
+  rc = factory->LoadKey(std::move(key_material), additional_params,
+                        std::move(hw_enforced), std::move(sw_enforced), key);
   if (rc != KM_ERROR_OK) {
     LOG(ERROR) << "Unable to load unwrapped key: " << rc;
   }
   return rc;
 }
 
-keymaster_error_t TpmKeymasterContext::AddRngEntropy(
-    const uint8_t* buffer, size_t size) const {
+keymaster_error_t TpmKeymasterContext::AddRngEntropy(const uint8_t* buffer,
+                                                     size_t size) const {
   return random_source_->AddRngEntropy(buffer, size);
 }
 
@@ -243,22 +273,25 @@
   return &enforcement_;
 }
 
-// Based on https://cs.android.com/android/platform/superproject/+/master:system/keymaster/contexts/pure_soft_keymaster_context.cpp;l=261;drc=8367d5351c4d417a11f49b12394b63a413faa02d
+// Based on
+// https://cs.android.com/android/platform/superproject/+/master:system/keymaster/contexts/pure_soft_keymaster_context.cpp;l=261;drc=8367d5351c4d417a11f49b12394b63a413faa02d
 
 keymaster::CertificateChain TpmKeymasterContext::GenerateAttestation(
     const keymaster::Key& key, const keymaster::AuthorizationSet& attest_params,
-    keymaster::UniquePtr<keymaster::Key> /* attest_key */,
-    const keymaster::KeymasterBlob& /* issuer_subject */,
+    keymaster::UniquePtr<keymaster::Key> attest_key,
+    const keymaster::KeymasterBlob& issuer_subject,
     keymaster_error_t* error) const {
   LOG(INFO) << "TODO(b/155697200): Link attestation back to the TPM";
   keymaster_algorithm_t key_algorithm;
   if (!key.authorizations().GetTagValue(keymaster::TAG_ALGORITHM,
                                         &key_algorithm)) {
+    LOG(ERROR) << "Cannot find key algorithm (TAG_ALGORITHM)";
     *error = KM_ERROR_UNKNOWN_ERROR;
     return {};
   }
 
   if ((key_algorithm != KM_ALGORITHM_RSA && key_algorithm != KM_ALGORITHM_EC)) {
+    LOG(ERROR) << "Invalid algorithm: " << key_algorithm;
     *error = KM_ERROR_INCOMPATIBLE_ALGORITHM;
     return {};
   }
@@ -277,12 +310,20 @@
   // hardware/interfaces/keymaster/4.1/vts/functional/DeviceUniqueAttestationTest.cpp:203
   // at commit 36dcf1a404a9cf07ca5a2a6ad92371507194fe1b .
   if (attest_params.find(keymaster::TAG_DEVICE_UNIQUE_ATTESTATION) != -1) {
+    LOG(ERROR) << "TAG_DEVICE_UNIQUE_ATTESTATION not supported";
     *error = KM_ERROR_UNIMPLEMENTED;
     return {};
   }
 
+  keymaster::AttestKeyInfo attest_key_info(attest_key, &issuer_subject, error);
+  if (*error != KM_ERROR_OK) {
+    LOG(ERROR)
+        << "Error creating attestation key info from given key and subject";
+    return {};
+  }
+
   return keymaster::generate_attestation(asymmetric_key, attest_params,
-                                         {} /* attest_key */,
+                                         std::move(attest_key_info),
                                          *attestation_context_, error);
 }
 
@@ -290,32 +331,332 @@
     const keymaster::Key& key, const keymaster::AuthorizationSet& cert_params,
     bool fake_signature, keymaster_error_t* error) const {
   keymaster_algorithm_t key_algorithm;
-  if (!key.authorizations().GetTagValue(keymaster::TAG_ALGORITHM, &key_algorithm)) {
-      *error = KM_ERROR_UNKNOWN_ERROR;
-      return {};
+  if (!key.authorizations().GetTagValue(keymaster::TAG_ALGORITHM,
+                                        &key_algorithm)) {
+    *error = KM_ERROR_UNKNOWN_ERROR;
+    return {};
   }
 
   if ((key_algorithm != KM_ALGORITHM_RSA && key_algorithm != KM_ALGORITHM_EC)) {
-      *error = KM_ERROR_INCOMPATIBLE_ALGORITHM;
-      return {};
+    *error = KM_ERROR_INCOMPATIBLE_ALGORITHM;
+    return {};
   }
 
-  // We have established that the given key has the correct algorithm, and because this is the
-  // SoftKeymasterContext we can assume that the Key is an AsymmetricKey. So we can downcast.
+  // We have established that the given key has the correct algorithm, and
+  // because this is the SoftKeymasterContext we can assume that the Key is an
+  // AsymmetricKey. So we can downcast.
   const keymaster::AsymmetricKey& asymmetric_key =
       static_cast<const keymaster::AsymmetricKey&>(key);
 
-  return generate_self_signed_cert(asymmetric_key, cert_params, fake_signature, error);
+  return generate_self_signed_cert(asymmetric_key, cert_params, fake_signature,
+                                   error);
 }
 
 keymaster_error_t TpmKeymasterContext::UnwrapKey(
-    const KeymasterKeyBlob&,
-    const KeymasterKeyBlob&,
-    const AuthorizationSet&,
-    const KeymasterKeyBlob&,
-    AuthorizationSet*,
-    keymaster_key_format_t*,
-    KeymasterKeyBlob*) const {
-  LOG(ERROR) << "TODO(b/155697375): Implement UnwrapKey";
-  return KM_ERROR_UNIMPLEMENTED;
+    const KeymasterKeyBlob& wrapped_key_blob,
+    const KeymasterKeyBlob& wrapping_key_blob,
+    const AuthorizationSet& wrapping_key_params,
+    const KeymasterKeyBlob& masking_key, AuthorizationSet* wrapped_key_params,
+    keymaster_key_format_t* wrapped_key_format,
+    KeymasterKeyBlob* wrapped_key_material) const {
+  keymaster_error_t error = KM_ERROR_OK;
+
+  if (wrapped_key_material == nullptr) {
+    return KM_ERROR_UNEXPECTED_NULL_POINTER;
+  }
+
+  // Parse wrapping key.
+  keymaster::UniquePtr<keymaster::Key> wrapping_key;
+  error = ParseKeyBlob(wrapping_key_blob, wrapping_key_params, &wrapping_key);
+  if (error != KM_ERROR_OK) {
+    return error;
+  }
+
+  keymaster::AuthProxy wrapping_key_auths(wrapping_key->hw_enforced(),
+                                          wrapping_key->sw_enforced());
+
+  // Check Wrapping Key Purpose
+  if (!wrapping_key_auths.Contains(keymaster::TAG_PURPOSE, KM_PURPOSE_WRAP)) {
+    LOG(ERROR) << "Wrapping key did not have KM_PURPOSE_WRAP";
+    return KM_ERROR_INCOMPATIBLE_PURPOSE;
+  }
+
+  // Check Padding mode is RSA_OAEP and digest is SHA_2_256 (spec
+  // mandated)
+  if (!wrapping_key_auths.Contains(keymaster::TAG_DIGEST,
+                                   KM_DIGEST_SHA_2_256)) {
+    LOG(ERROR) << "Wrapping key lacks authorization for SHA2-256";
+    return KM_ERROR_INCOMPATIBLE_DIGEST;
+  }
+  if (!wrapping_key_auths.Contains(keymaster::TAG_PADDING, KM_PAD_RSA_OAEP)) {
+    LOG(ERROR) << "Wrapping key lacks authorization for padding OAEP";
+    return KM_ERROR_INCOMPATIBLE_PADDING_MODE;
+  }
+
+  // Check that that was also the padding mode and digest specified
+  if (!wrapping_key_params.Contains(keymaster::TAG_DIGEST,
+                                    KM_DIGEST_SHA_2_256)) {
+    LOG(ERROR) << "Wrapping key must use SHA2-256";
+    return KM_ERROR_INCOMPATIBLE_DIGEST;
+  }
+  if (!wrapping_key_params.Contains(keymaster::TAG_PADDING, KM_PAD_RSA_OAEP)) {
+    LOG(ERROR) << "Wrapping key must use OAEP padding";
+    return KM_ERROR_INCOMPATIBLE_PADDING_MODE;
+  }
+
+  // Parse wrapped key data.
+  KeymasterBlob iv;
+  KeymasterKeyBlob transit_key;
+  KeymasterKeyBlob secure_key;
+  KeymasterBlob tag;
+  KeymasterBlob wrapped_key_description;
+  error = parse_wrapped_key(wrapped_key_blob, &iv, &transit_key, &secure_key,
+                            &tag, wrapped_key_params, wrapped_key_format,
+                            &wrapped_key_description);
+  if (error != KM_ERROR_OK) {
+    return error;
+  }
+
+  // Decrypt encryptedTransportKey (transit_key) with wrapping_key
+  keymaster::OperationFactory* operation_factory =
+      wrapping_key->key_factory()->GetOperationFactory(KM_PURPOSE_DECRYPT);
+  if (operation_factory == NULL) {
+    return KM_ERROR_UNKNOWN_ERROR;
+  }
+
+  AuthorizationSet out_params;
+  keymaster::OperationPtr operation(operation_factory->CreateOperation(
+      std::move(*wrapping_key), wrapping_key_params, &error));
+  if ((operation.get() == nullptr) || (error != KM_ERROR_OK)) {
+    return error;
+  }
+
+  error = operation->Begin(wrapping_key_params, &out_params);
+  if (error != KM_ERROR_OK) {
+    return error;
+  }
+
+  keymaster::Buffer input;
+  if (!input.Reinitialize(transit_key.key_material,
+                          transit_key.key_material_size)) {
+    return KM_ERROR_MEMORY_ALLOCATION_FAILED;
+  }
+
+  keymaster::Buffer output;
+  error = operation->Finish(wrapping_key_params, input,
+                            keymaster::Buffer() /* signature */, &out_params,
+                            &output);
+  if (error != KM_ERROR_OK) {
+    return error;
+  }
+
+  // decrypt the encrypted key material with the transit key
+  KeymasterKeyBlob transport_key = {output.peek_read(),
+                                    output.available_read()};
+
+  // XOR the transit key with the masking key
+  if (transport_key.key_material_size != masking_key.key_material_size) {
+    return KM_ERROR_INVALID_ARGUMENT;
+  }
+  for (size_t i = 0; i < transport_key.key_material_size; i++) {
+    transport_key.writable_data()[i] ^= masking_key.key_material[i];
+  }
+
+  auto transport_key_authorizations =
+      keymaster::AuthorizationSetBuilder()
+          .AesEncryptionKey(256)
+          .Padding(KM_PAD_NONE)
+          .Authorization(keymaster::TAG_BLOCK_MODE, KM_MODE_GCM)
+          .Authorization(keymaster::TAG_NONCE, iv)
+          .Authorization(keymaster::TAG_MIN_MAC_LENGTH, 128)
+          .build();
+  if (transport_key_authorizations.is_valid() != AuthorizationSet::Error::OK) {
+    return TranslateAuthorizationSetError(
+        transport_key_authorizations.is_valid());
+  }
+
+  auto gcm_params = keymaster::AuthorizationSetBuilder()
+                        .Padding(KM_PAD_NONE)
+                        .Authorization(keymaster::TAG_BLOCK_MODE, KM_MODE_GCM)
+                        .Authorization(keymaster::TAG_NONCE, iv)
+                        .Authorization(keymaster::TAG_MAC_LENGTH, 128)
+                        .build();
+  if (gcm_params.is_valid() != AuthorizationSet::Error::OK) {
+    return TranslateAuthorizationSetError(
+        transport_key_authorizations.is_valid());
+  }
+
+  auto aes_factory = GetKeyFactory(KM_ALGORITHM_AES);
+  if (!aes_factory) {
+    return KM_ERROR_UNKNOWN_ERROR;
+  }
+
+  keymaster::UniquePtr<keymaster::Key> aes_transport_key;
+  error = aes_factory->LoadKey(std::move(transport_key), gcm_params,
+                               std::move(transport_key_authorizations),
+                               AuthorizationSet(), &aes_transport_key);
+  if (error != KM_ERROR_OK) {
+    return error;
+  }
+
+  keymaster::OperationFactory* aes_operation_factory =
+      GetOperationFactory(KM_ALGORITHM_AES, KM_PURPOSE_DECRYPT);
+  if (!aes_operation_factory) {
+    return KM_ERROR_UNKNOWN_ERROR;
+  }
+
+  keymaster::OperationPtr aes_operation(aes_operation_factory->CreateOperation(
+      std::move(*aes_transport_key), gcm_params, &error));
+  if (!aes_operation.get()) {
+    return error;
+  }
+
+  error = aes_operation->Begin(gcm_params, &out_params);
+  if (error != KM_ERROR_OK) {
+    return error;
+  }
+
+  size_t total_key_size = secure_key.key_material_size + tag.data_length;
+  keymaster::Buffer plaintext_key;
+  if (!plaintext_key.Reinitialize(total_key_size)) {
+    return KM_ERROR_MEMORY_ALLOCATION_FAILED;
+  }
+  keymaster::Buffer encrypted_key;
+  if (!encrypted_key.Reinitialize(total_key_size)) {
+    return KM_ERROR_MEMORY_ALLOCATION_FAILED;
+  }
+
+  // Concatenate key data and authentication tag.
+  if (!encrypted_key.write(secure_key.key_material,
+                           secure_key.key_material_size)) {
+    return KM_ERROR_UNKNOWN_ERROR;
+  }
+  if (!encrypted_key.write(tag.data, tag.data_length)) {
+    return KM_ERROR_UNKNOWN_ERROR;
+  }
+
+  auto update_params = keymaster::AuthorizationSetBuilder()
+                           .Authorization(keymaster::TAG_ASSOCIATED_DATA,
+                                          wrapped_key_description.data,
+                                          wrapped_key_description.data_length)
+                           .build();
+  if (update_params.is_valid() != AuthorizationSet::Error::OK) {
+    return TranslateAuthorizationSetError(update_params.is_valid());
+  }
+
+  size_t update_consumed = 0;
+  AuthorizationSet update_outparams;
+  error = aes_operation->Update(update_params, encrypted_key, &update_outparams,
+                                &plaintext_key, &update_consumed);
+  if (error != KM_ERROR_OK) {
+    return error;
+  }
+
+  AuthorizationSet finish_params;
+  AuthorizationSet finish_out_params;
+  keymaster::Buffer finish_input;
+  error = aes_operation->Finish(finish_params, finish_input,
+                                keymaster::Buffer() /* signature */,
+                                &finish_out_params, &plaintext_key);
+  if (error != KM_ERROR_OK) {
+    return error;
+  }
+
+  *wrapped_key_material = {plaintext_key.peek_read(),
+                           plaintext_key.available_read()};
+  if (!wrapped_key_material->key_material && plaintext_key.peek_read()) {
+    return KM_ERROR_MEMORY_ALLOCATION_FAILED;
+  }
+
+  return error;
 }
+
+keymaster::RemoteProvisioningContext*
+TpmKeymasterContext::GetRemoteProvisioningContext() const {
+  return remote_provisioning_context_.get();
+}
+
+std::string ToHexString(const std::vector<uint8_t>& binary) {
+  std::string hex;
+  hex.reserve(binary.size() * 2);
+  for (uint8_t byte : binary) {
+    char buf[8];
+    snprintf(buf, sizeof(buf), "%02x", byte);
+    hex.append(buf);
+  }
+  return hex;
+}
+
+keymaster_error_t TpmKeymasterContext::SetVerifiedBootInfo(
+    std::string_view verified_boot_state, std::string_view bootloader_state,
+    const std::vector<uint8_t>& vbmeta_digest) {
+  if (verified_boot_state_ && verified_boot_state != *verified_boot_state_) {
+    LOG(ERROR) << "Invalid set verified boot state attempt. "
+               << "Old verified boot state: \"" << *verified_boot_state_
+               << "\","
+               << "new verified boot state: \"" << verified_boot_state << "\"";
+    return KM_ERROR_INVALID_ARGUMENT;
+  }
+  if (bootloader_state_ && bootloader_state != *bootloader_state_) {
+    LOG(ERROR) << "Invalid set bootloader state attempt. "
+               << "Old bootloader state: \"" << *bootloader_state_ << "\","
+               << "new bootloader state: \"" << bootloader_state << "\"";
+    return KM_ERROR_INVALID_ARGUMENT;
+  }
+  if (vbmeta_digest_ && vbmeta_digest != *vbmeta_digest_) {
+    LOG(ERROR) << "Invalid set vbmeta digest state attempt. "
+               << "Old vbmeta digest state: \"" << ToHexString(*vbmeta_digest_)
+               << "\","
+               << "new vbmeta digest state: \"" << ToHexString(vbmeta_digest)
+               << "\"";
+    return KM_ERROR_INVALID_ARGUMENT;
+  }
+  verified_boot_state_ = verified_boot_state;
+  bootloader_state_ = bootloader_state;
+  vbmeta_digest_ = vbmeta_digest;
+  attestation_context_->SetVerifiedBootInfo(verified_boot_state,
+                                            bootloader_state, vbmeta_digest);
+  remote_provisioning_context_->SetVerifiedBootInfo(
+      verified_boot_state, bootloader_state, vbmeta_digest);
+  return KM_ERROR_OK;
+}
+
+keymaster_error_t TpmKeymasterContext::SetVendorPatchlevel(
+    uint32_t vendor_patchlevel) {
+  if (vendor_patchlevel_.has_value() &&
+      vendor_patchlevel != vendor_patchlevel_.value()) {
+    // Can't set patchlevel to a different value.
+    LOG(ERROR) << "Invalid set vendor patchlevel attempt. Old patchlevel: \""
+               << *vendor_patchlevel_ << "\", new patchlevel: \""
+               << vendor_patchlevel << "\"";
+    return KM_ERROR_INVALID_ARGUMENT;
+  }
+  vendor_patchlevel_ = vendor_patchlevel;
+  remote_provisioning_context_->SetVendorPatchlevel(vendor_patchlevel);
+  return key_blob_maker_->SetVendorPatchlevel(*vendor_patchlevel_);
+}
+
+keymaster_error_t TpmKeymasterContext::SetBootPatchlevel(
+    uint32_t boot_patchlevel) {
+  if (boot_patchlevel_.has_value() &&
+      boot_patchlevel != boot_patchlevel_.value()) {
+    // Can't set patchlevel to a different value.
+    LOG(ERROR) << "Invalid set boot patchlevel attempt. Old patchlevel: \""
+               << *boot_patchlevel_ << "\", new patchlevel: \""
+               << boot_patchlevel << "\"";
+    return KM_ERROR_INVALID_ARGUMENT;
+  }
+  boot_patchlevel_ = boot_patchlevel;
+  remote_provisioning_context_->SetBootPatchlevel(boot_patchlevel);
+  return key_blob_maker_->SetBootPatchlevel(*boot_patchlevel_);
+}
+
+std::optional<uint32_t> TpmKeymasterContext::GetVendorPatchlevel() const {
+  return vendor_patchlevel_;
+}
+
+std::optional<uint32_t> TpmKeymasterContext::GetBootPatchlevel() const {
+  return boot_patchlevel_;
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_keymaster_context.h b/host/commands/secure_env/tpm_keymaster_context.h
index 54ba973..dbcdcb4 100644
--- a/host/commands/secure_env/tpm_keymaster_context.h
+++ b/host/commands/secure_env/tpm_keymaster_context.h
@@ -23,10 +23,13 @@
 
 #include "tpm_attestation_record.h"
 
+namespace cuttlefish {
+
 class TpmAttestationRecordContext;
 class TpmResourceManager;
 class TpmKeyBlobMaker;
 class TpmRandomSource;
+class TpmRemoteProvisioningContext;
 
 /**
  * Implementation of KeymasterContext that wraps its keys with a TPM.
@@ -35,17 +38,25 @@
  * https://cs.android.com/android/platform/superproject/+/master:system/keymaster/include/keymaster/keymaster_context.h;drc=821acb74d7febb886a9b7cefee4ee3df4cc8c556
  */
 class TpmKeymasterContext : public keymaster::KeymasterContext {
-private:
+ private:
   TpmResourceManager& resource_manager_;
   keymaster::KeymasterEnforcement& enforcement_;
   std::unique_ptr<TpmKeyBlobMaker> key_blob_maker_;
   std::unique_ptr<TpmRandomSource> random_source_;
   std::unique_ptr<TpmAttestationRecordContext> attestation_context_;
-  std::map<keymaster_algorithm_t, std::unique_ptr<keymaster::KeyFactory>> key_factories_;
+  std::unique_ptr<TpmRemoteProvisioningContext> remote_provisioning_context_;
+  std::map<keymaster_algorithm_t, std::unique_ptr<keymaster::KeyFactory>>
+      key_factories_;
   std::vector<keymaster_algorithm_t> supported_algorithms_;
   uint32_t os_version_;
   uint32_t os_patchlevel_;
-public:
+  std::optional<uint32_t> vendor_patchlevel_;
+  std::optional<uint32_t> boot_patchlevel_;
+  std::optional<std::string> bootloader_state_;
+  std::optional<std::string> verified_boot_state_;
+  std::optional<std::vector<uint8_t>> vbmeta_digest_;
+
+ public:
   TpmKeymasterContext(TpmResourceManager&, keymaster::KeymasterEnforcement&);
   ~TpmKeymasterContext() = default;
 
@@ -53,14 +64,14 @@
     return attestation_context_->GetKmVersion();
   }
 
-  keymaster_error_t SetSystemVersion(
-      uint32_t os_version, uint32_t os_patchlevel) override;
-  void GetSystemVersion(
-      uint32_t* os_version, uint32_t* os_patchlevel) const override;
+  keymaster_error_t SetSystemVersion(uint32_t os_version,
+                                     uint32_t os_patchlevel) override;
+  void GetSystemVersion(uint32_t* os_version,
+                        uint32_t* os_patchlevel) const override;
 
   const keymaster::KeyFactory* GetKeyFactory(
       keymaster_algorithm_t algorithm) const override;
-  const keymaster::OperationFactory* GetOperationFactory(
+  keymaster::OperationFactory* GetOperationFactory(
       keymaster_algorithm_t algorithm,
       keymaster_purpose_t purpose) const override;
   const keymaster_algorithm_t* GetSupportedAlgorithms(
@@ -76,11 +87,15 @@
       const keymaster::AuthorizationSet& additional_params,
       keymaster::UniquePtr<keymaster::Key>* key) const override;
 
-  keymaster_error_t AddRngEntropy(
-      const uint8_t* buf, size_t length) const override;
+  keymaster_error_t AddRngEntropy(const uint8_t* buf,
+                                  size_t length) const override;
 
   keymaster::KeymasterEnforcement* enforcement_policy() override;
 
+  keymaster::AttestationContext* attestation_context() override {
+    return attestation_context_.get();
+  }
+
   keymaster::CertificateChain GenerateAttestation(
       const keymaster::Key& key,
       const keymaster::AuthorizationSet& attest_params,
@@ -89,10 +104,8 @@
       keymaster_error_t* error) const override;
 
   keymaster::CertificateChain GenerateSelfSignedCertificate(
-      const keymaster::Key& key,
-      const keymaster::AuthorizationSet& cert_params,
-      bool fake_signature,
-      keymaster_error_t* error) const override;
+      const keymaster::Key& key, const keymaster::AuthorizationSet& cert_params,
+      bool fake_signature, keymaster_error_t* error) const override;
 
   keymaster_error_t UnwrapKey(
       const keymaster::KeymasterKeyBlob& wrapped_key_blob,
@@ -102,4 +115,18 @@
       keymaster::AuthorizationSet* wrapped_key_params,
       keymaster_key_format_t* wrapped_key_format,
       keymaster::KeymasterKeyBlob* wrapped_key_material) const override;
+
+  keymaster::RemoteProvisioningContext* GetRemoteProvisioningContext()
+      const override;
+
+  keymaster_error_t SetVerifiedBootInfo(
+      std::string_view verified_boot_state, std::string_view bootloader_state,
+      const std::vector<uint8_t>& vbmeta_digest) override;
+
+  keymaster_error_t SetVendorPatchlevel(uint32_t vendor_patchlevel) override;
+  keymaster_error_t SetBootPatchlevel(uint32_t boot_patchlevel) override;
+  std::optional<uint32_t> GetVendorPatchlevel() const override;
+  std::optional<uint32_t> GetBootPatchlevel() const override;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_keymaster_enforcement.cpp b/host/commands/secure_env/tpm_keymaster_enforcement.cpp
index f5b8903..a5368c5 100644
--- a/host/commands/secure_env/tpm_keymaster_enforcement.cpp
+++ b/host/commands/secure_env/tpm_keymaster_enforcement.cpp
@@ -15,21 +15,21 @@
 
 #include "host/commands/secure_env/tpm_keymaster_enforcement.h"
 
+#include <android-base/endian.h>
 #include <android-base/logging.h>
-#if defined(__BIONIC__)
-#include <sys/endian.h> // for be64toh
-#endif
 
 #include "host/commands/secure_env/primary_key_builder.h"
 #include "host/commands/secure_env/tpm_hmac.h"
 #include "host/commands/secure_env/tpm_key_blob_maker.h"
 #include "host/commands/secure_env/tpm_random_source.h"
 
-using keymaster::km_id_t;
+namespace cuttlefish {
+
 using keymaster::HmacSharingParameters;
 using keymaster::HmacSharingParametersArray;
 using keymaster::KeymasterBlob;
 using keymaster::KeymasterEnforcement;
+using keymaster::km_id_t;
 using keymaster::VerifyAuthorizationRequest;
 using keymaster::VerifyAuthorizationResponse;
 namespace {
@@ -46,9 +46,9 @@
 }
 }  // namespace
 class CompareHmacSharingParams {
-public:
-  bool operator()(
-      const HmacSharingParameters& a, const HmacSharingParameters& b) const {
+ public:
+  bool operator()(const HmacSharingParameters& a,
+                  const HmacSharingParameters& b) const {
     if (a.seed.data_length != b.seed.data_length) {
       return a.seed.data_length < b.seed.data_length;
     }
@@ -86,11 +86,9 @@
     TpmResourceManager& resource_manager, TpmGatekeeper& gatekeeper)
     : KeymasterEnforcement(64, 64),
       resource_manager_(resource_manager),
-      gatekeeper_(gatekeeper) {
-}
+      gatekeeper_(gatekeeper) {}
 
-TpmKeymasterEnforcement::~TpmKeymasterEnforcement() {
-}
+TpmKeymasterEnforcement::~TpmKeymasterEnforcement() {}
 
 bool TpmKeymasterEnforcement::activation_date_valid(
     uint64_t activation_date) const {
@@ -99,13 +97,13 @@
 
 bool TpmKeymasterEnforcement::expiration_date_passed(
     uint64_t expiration_date) const {
-  return expiration_date > get_wall_clock_time_ms();
+  return expiration_date < get_wall_clock_time_ms();
 }
 
-bool TpmKeymasterEnforcement::auth_token_timed_out(
-    const hw_auth_token_t& token, uint32_t timeout) const {
+bool TpmKeymasterEnforcement::auth_token_timed_out(const hw_auth_token_t& token,
+                                                   uint32_t timeout) const {
   // timeout comes in seconds, token.timestamp comes in milliseconds
-  uint64_t timeout_ms = 1000 * (uint64_t) timeout;
+  uint64_t timeout_ms = 1000 * (uint64_t)timeout;
   return (be64toh(token.timestamp) + timeout_ms) < get_current_time_ms();
 }
 
@@ -132,32 +130,24 @@
    * GateKeeper::MintAuthToken
    */
 
-  const uint8_t *auth_token_key = nullptr;
+  const uint8_t* auth_token_key = nullptr;
   uint32_t auth_token_key_len = 0;
   if (!gatekeeper_.GetAuthTokenKey(&auth_token_key, &auth_token_key_len)) {
     LOG(WARNING) << "Unable to get gatekeeper auth token";
     return false;
   }
 
+  constexpr uint32_t hashable_length =
+      sizeof(token.version) + sizeof(token.challenge) + sizeof(token.user_id) +
+      sizeof(token.authenticator_id) + sizeof(token.authenticator_type) +
+      sizeof(token.timestamp);
 
-  constexpr uint32_t hashable_length = sizeof(token.version) +
-                                       sizeof(token.challenge) +
-                                       sizeof(token.user_id) +
-                                       sizeof(token.authenticator_id) +
-                                       sizeof(token.authenticator_type) +
-                                       sizeof(token.timestamp);
-
-  static_assert(
-      offsetof(hw_auth_token_t, hmac) == hashable_length,
-      "hw_auth_token_t does not appear to be packed");
-
+  static_assert(offsetof(hw_auth_token_t, hmac) == hashable_length,
+                "hw_auth_token_t does not appear to be packed");
 
   gatekeeper_.ComputeSignature(
-      comparison_token.hmac,
-      sizeof(comparison_token.hmac),
-      auth_token_key,
-      auth_token_key_len,
-      reinterpret_cast<uint8_t*>(&comparison_token),
+      comparison_token.hmac, sizeof(comparison_token.hmac), auth_token_key,
+      auth_token_key_len, reinterpret_cast<uint8_t*>(&comparison_token),
       hashable_length);
 
   static_assert(sizeof(token.hmac) == sizeof(comparison_token.hmac));
@@ -170,9 +160,8 @@
   if (!have_saved_params_) {
     saved_params_.seed = {};
     TpmRandomSource random_source{resource_manager_.Esys()};
-    auto rc =
-        random_source.GenerateRandom(
-            saved_params_.nonce, sizeof(saved_params_.nonce));
+    auto rc = random_source.GenerateRandom(saved_params_.nonce,
+                                           sizeof(saved_params_.nonce));
     if (rc != KM_ERROR_OK) {
       LOG(ERROR) << "Failed to generate HmacSharingParameters nonce";
       return rc;
@@ -185,18 +174,15 @@
 }
 
 keymaster_error_t TpmKeymasterEnforcement::ComputeSharedHmac(
-    const HmacSharingParametersArray& hmac_array,
-    KeymasterBlob* sharingCheck) {
+    const HmacSharingParametersArray& hmac_array, KeymasterBlob* sharingCheck) {
   std::set<HmacSharingParameters, CompareHmacSharingParams> sorted_hmac_inputs;
   bool found_mine = false;
   for (int i = 0; i < hmac_array.num_params; i++) {
     HmacSharingParameters sharing_params;
     sharing_params.seed =
         keymaster::KeymasterBlob(hmac_array.params_array[i].seed);
-    memcpy(
-        sharing_params.nonce,
-        hmac_array.params_array[i].nonce,
-        sizeof(sharing_params.nonce));
+    memcpy(sharing_params.nonce, hmac_array.params_array[i].nonce,
+           sizeof(sharing_params.nonce));
     found_mine = found_mine || (sharing_params == saved_params_);
     sorted_hmac_inputs.emplace(std::move(sharing_params));
   }
@@ -218,7 +204,6 @@
     }
   }
 
-
   auto signing_key_builder = PrimaryKeyBuilder();
   signing_key_builder.SigningKey();
   signing_key_builder.UniqueData(std::string(unique_data, sizeof(unique_data)));
@@ -230,12 +215,9 @@
 
   static const uint8_t signing_input[] = "Keymaster HMAC Verification";
 
-  auto hmac = TpmHmac(
-      resource_manager_,
-      signing_key->get(),
-      TpmAuth(ESYS_TR_PASSWORD),
-      signing_input,
-      sizeof(signing_input));
+  auto hmac =
+      TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              signing_input, sizeof(signing_input));
 
   if (!hmac) {
     LOG(ERROR) << "Unable to complete signing check";
@@ -260,10 +242,10 @@
   response.token.timestamp = get_current_time_ms();
   response.token.security_level = SecurityLevel();
 
-  VerificationData verify_data {
-    .challenge = response.token.challenge,
-    .timestamp = response.token.timestamp,
-    .security_level = response.token.security_level,
+  VerificationData verify_data{
+      .challenge = response.token.challenge,
+      .timestamp = response.token.timestamp,
+      .security_level = response.token.security_level,
   };
 
   auto signing_key_builder = PrimaryKeyBuilder();
@@ -274,12 +256,9 @@
     LOG(ERROR) << "Could not make signing key for verifying authorization";
     return response;
   }
-  auto hmac = TpmHmac(
-      resource_manager_,
-      signing_key->get(),
-      TpmAuth(ESYS_TR_PASSWORD),
-      reinterpret_cast<uint8_t*>(&verify_data),
-      sizeof(verify_data));
+  auto hmac =
+      TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              reinterpret_cast<uint8_t*>(&verify_data), sizeof(verify_data));
 
   if (!hmac) {
     LOG(ERROR) << "Could not calculate verification hmac";
@@ -294,18 +273,56 @@
   return response;
 }
 
-bool TpmKeymasterEnforcement::CreateKeyId(
-    const keymaster_key_blob_t& key_blob, km_id_t* keyid) const {
-  keymaster::AuthorizationSet hw_enforced;
-  keymaster::AuthorizationSet sw_enforced;
-  keymaster::KeymasterKeyBlob key_material;
-  auto rc =
-      TpmKeyBlobMaker(resource_manager_)
-          .UnwrapKeyBlob(key_blob, &hw_enforced, &sw_enforced, &key_material);
-  if (rc != KM_ERROR_OK) {
-    LOG(ERROR) << "Could not unwrap key: " << rc;
-    return false;
+keymaster_error_t TpmKeymasterEnforcement::GenerateTimestampToken(
+    keymaster::TimestampToken* token) {
+  token->timestamp = get_current_time_ms();
+  token->security_level = SecurityLevel();
+  token->mac = KeymasterBlob();
+
+  auto signing_key_builder = PrimaryKeyBuilder();
+  signing_key_builder.SigningKey();
+  signing_key_builder.UniqueData("timestamp_token");
+  auto signing_key = signing_key_builder.CreateKey(resource_manager_);
+  if (!signing_key) {
+    LOG(ERROR) << "Could not make signing key for verifying authorization";
+    return KM_ERROR_UNKNOWN_ERROR;
   }
+  std::vector<uint8_t> token_buf_to_sign(token->SerializedSize(), 0);
+  auto hmac =
+      TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              token_buf_to_sign.data(), token_buf_to_sign.size());
+  if (!hmac) {
+    LOG(ERROR) << "Could not calculate timestamp token hmac";
+    return KM_ERROR_UNKNOWN_ERROR;
+  } else if (hmac->size == 0) {
+    LOG(ERROR) << "hmac was too short";
+    return KM_ERROR_UNKNOWN_ERROR;
+  }
+  token->mac = KeymasterBlob(hmac->buffer, hmac->size);
+
+  return KM_ERROR_OK;
+}
+
+keymaster::KmErrorOr<std::array<uint8_t, 32>>
+TpmKeymasterEnforcement::ComputeHmac(
+    const std::vector<uint8_t>& data_to_mac) const {
+  std::array<uint8_t, 32> result;
+
+  const uint8_t* auth_token_key = nullptr;
+  uint32_t auth_token_key_len = 0;
+  if (!gatekeeper_.GetAuthTokenKey(&auth_token_key, &auth_token_key_len)) {
+    LOG(WARNING) << "Unable to get gatekeeper auth token";
+    return KM_ERROR_UNKNOWN_ERROR;
+  }
+
+  gatekeeper_.ComputeSignature(result.data(), result.size(), auth_token_key,
+                               auth_token_key_len, data_to_mac.data(),
+                               data_to_mac.size());
+  return result;
+}
+
+bool TpmKeymasterEnforcement::CreateKeyId(const keymaster_key_blob_t& key_blob,
+                                          km_id_t* keyid) const {
   auto signing_key_builder = PrimaryKeyBuilder();
   signing_key_builder.SigningKey();
   signing_key_builder.UniqueData("key_id");
@@ -314,12 +331,9 @@
     LOG(ERROR) << "Could not make signing key for key id";
     return false;
   }
-  auto hmac = TpmHmac(
-      resource_manager_,
-      signing_key->get(),
-      TpmAuth(ESYS_TR_PASSWORD),
-      key_material.key_material,
-      key_material.key_material_size);
+  auto hmac =
+      TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              key_blob.key_material, key_blob.key_material_size);
   if (!hmac) {
     LOG(ERROR) << "Failed to make a signature for a key id";
     return false;
@@ -332,3 +346,5 @@
   memcpy(keyid, hmac->buffer, sizeof(km_id_t));
   return true;
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_keymaster_enforcement.h b/host/commands/secure_env/tpm_keymaster_enforcement.h
index 1d6a1e5..1178932 100644
--- a/host/commands/secure_env/tpm_keymaster_enforcement.h
+++ b/host/commands/secure_env/tpm_keymaster_enforcement.h
@@ -20,28 +20,30 @@
 #include "host/commands/secure_env/tpm_gatekeeper.h"
 #include "host/commands/secure_env/tpm_resource_manager.h"
 
+namespace cuttlefish {
+
 /**
  * Implementation of keymaster::KeymasterEnforcement that depends on having a
  * TPM available. See the definitions in
  * system/keymaster/include/keymaster/keymaster_enforcement.h
  */
 class TpmKeymasterEnforcement : public keymaster::KeymasterEnforcement {
-public:
-  TpmKeymasterEnforcement(
-      TpmResourceManager& resource_manager, TpmGatekeeper& gatekeeper);
+ public:
+  TpmKeymasterEnforcement(TpmResourceManager& resource_manager,
+                          TpmGatekeeper& gatekeeper);
   ~TpmKeymasterEnforcement();
 
   bool activation_date_valid(uint64_t activation_date) const override;
   bool expiration_date_passed(uint64_t expiration_date) const override;
-  bool auth_token_timed_out(
-      const hw_auth_token_t& token, uint32_t timeout) const override;
+  bool auth_token_timed_out(const hw_auth_token_t& token,
+                            uint32_t timeout) const override;
   uint64_t get_current_time_ms() const override;
 
   keymaster_security_level_t SecurityLevel() const override;
   bool ValidateTokenSignature(const hw_auth_token_t& token) const override;
 
   keymaster_error_t GetHmacSharingParameters(
-        keymaster::HmacSharingParameters* params) override;
+      keymaster::HmacSharingParameters* params) override;
   keymaster_error_t ComputeSharedHmac(
       const keymaster::HmacSharingParametersArray& params_array,
       keymaster::KeymasterBlob* sharingCheck) override;
@@ -49,13 +51,20 @@
   keymaster::VerifyAuthorizationResponse VerifyAuthorization(
       const keymaster::VerifyAuthorizationRequest& request) override;
 
-  bool CreateKeyId(
-      const keymaster_key_blob_t& key_blob,
-      keymaster::km_id_t* keyid) const override;
+  keymaster_error_t GenerateTimestampToken(
+      keymaster::TimestampToken* token) override;
 
-private:
+  keymaster::KmErrorOr<std::array<uint8_t, 32>> ComputeHmac(
+      const std::vector<uint8_t>& data_to_mac) const override;
+
+  bool CreateKeyId(const keymaster_key_blob_t& key_blob,
+                   keymaster::km_id_t* keyid) const override;
+
+ private:
   TpmResourceManager& resource_manager_;
   TpmGatekeeper& gatekeeper_;
   bool have_saved_params_ = false;
   keymaster::HmacSharingParameters saved_params_;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_random_source.cpp b/host/commands/secure_env/tpm_random_source.cpp
index 8695e4a..569edbc 100644
--- a/host/commands/secure_env/tpm_random_source.cpp
+++ b/host/commands/secure_env/tpm_random_source.cpp
@@ -19,6 +19,8 @@
 #include "tss2/tss2_esys.h"
 #include "tss2/tss2_rc.h"
 
+namespace cuttlefish {
+
 TpmRandomSource::TpmRandomSource(ESYS_CONTEXT* esys) : esys_(esys) {
 }
 
@@ -62,6 +64,11 @@
 
 keymaster_error_t TpmRandomSource::AddRngEntropy(
     const uint8_t* buffer, size_t size) const {
+  if (size > 2048) {
+    // IKeyMintDevice.aidl specifies that there's an upper limit of 2KiB.
+    return KM_ERROR_INVALID_INPUT_LENGTH;
+  }
+
   TPM2B_SENSITIVE_DATA in_data;
   while (size > MAX_STIR_RANDOM_BUFFER_SIZE) {
     memcpy(in_data.buffer, buffer, MAX_STIR_RANDOM_BUFFER_SIZE);
@@ -97,3 +104,5 @@
   }
   return KM_ERROR_OK;
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_random_source.h b/host/commands/secure_env/tpm_random_source.h
index 2e7d8a1..c9a91c7 100644
--- a/host/commands/secure_env/tpm_random_source.h
+++ b/host/commands/secure_env/tpm_random_source.h
@@ -19,6 +19,8 @@
 
 struct ESYS_CONTEXT;
 
+namespace cuttlefish {
+
 /**
  * Secure random number generator, pulling data from a TPM.
  *
@@ -36,3 +38,5 @@
 private:
   ESYS_CONTEXT* esys_;
 };
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_remote_provisioning_context.cpp b/host/commands/secure_env/tpm_remote_provisioning_context.cpp
new file mode 100644
index 0000000..87bb172
--- /dev/null
+++ b/host/commands/secure_env/tpm_remote_provisioning_context.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <cassert>
+#include <optional>
+
+#include <android-base/logging.h>
+#include <keymaster/cppcose/cppcose.h>
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/hkdf.h>
+#include <openssl/rand.h>
+
+#include "host/commands/secure_env/primary_key_builder.h"
+#include "host/commands/secure_env/tpm_hmac.h"
+#include "tpm_remote_provisioning_context.h"
+#include "tpm_resource_manager.h"
+
+using namespace cppcose;
+
+namespace cuttlefish {
+
+TpmRemoteProvisioningContext::TpmRemoteProvisioningContext(
+    TpmResourceManager& resource_manager)
+    : resource_manager_(resource_manager) {
+  std::tie(devicePrivKey_, bcc_) = GenerateBcc(/*testMode=*/false);
+}
+
+std::vector<uint8_t> TpmRemoteProvisioningContext::DeriveBytesFromHbk(
+    const std::string& context, size_t num_bytes) const {
+  PrimaryKeyBuilder key_builder;
+  key_builder.SigningKey();
+  key_builder.UniqueData("HardwareBoundKey");
+  TpmObjectSlot key = key_builder.CreateKey(resource_manager_);
+
+  auto hbk =
+      TpmHmac(resource_manager_, key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              reinterpret_cast<const uint8_t*>(context.data()), context.size());
+
+  std::vector<uint8_t> result(num_bytes);
+  if (!HKDF(result.data(), num_bytes,              //
+            EVP_sha256(),                          //
+            hbk->buffer, hbk->size,                //
+            nullptr /* salt */, 0 /* salt len */,  //
+            reinterpret_cast<const uint8_t*>(context.data()), context.size())) {
+    // Should never fail. Even if it could the API has no way of reporting the
+    // error.
+    LOG(ERROR) << "Error calculating HMAC: " << ERR_peek_last_error();
+  }
+
+  return result;
+}
+
+std::unique_ptr<cppbor::Map> TpmRemoteProvisioningContext::CreateDeviceInfo()
+    const {
+  auto result = std::make_unique<cppbor::Map>();
+  result->add(cppbor::Tstr("brand"), cppbor::Tstr("Google"));
+  result->add(cppbor::Tstr("manufacturer"), cppbor::Tstr("Google"));
+  result->add(cppbor::Tstr("product"),
+              cppbor::Tstr("Cuttlefish Virtual Device"));
+  result->add(cppbor::Tstr("model"), cppbor::Tstr("Virtual Device"));
+  result->add(cppbor::Tstr("device"), cppbor::Tstr("Virtual Device"));
+  if (bootloader_state_) {
+    result->add(cppbor::Tstr("bootloader_state"),
+                cppbor::Tstr(*bootloader_state_));
+  }
+  if (verified_boot_state_) {
+    result->add(cppbor::Tstr("vb_state"), cppbor::Tstr(*verified_boot_state_));
+  }
+  if (vbmeta_digest_) {
+    result->add(cppbor::Tstr("vbmeta_digest"), cppbor::Bstr(*vbmeta_digest_));
+  }
+  if (os_version_) {
+    result->add(cppbor::Tstr("os_version"),
+                cppbor::Tstr(std::to_string(*os_version_)));
+  }
+  if (os_patchlevel_) {
+    result->add(cppbor::Tstr("system_patch_level"),
+                cppbor::Uint(*os_patchlevel_));
+  }
+  if (boot_patchlevel_) {
+    result->add(cppbor::Tstr("boot_patch_level"),
+                cppbor::Uint(*boot_patchlevel_));
+  }
+  if (vendor_patchlevel_) {
+    result->add(cppbor::Tstr("vendor_patch_level"),
+                cppbor::Uint(*vendor_patchlevel_));
+  }
+  result->add(cppbor::Tstr("version"), cppbor::Uint(2));
+  result->add(cppbor::Tstr("fused"), cppbor::Uint(0));
+  result->add(cppbor::Tstr("security_level"), cppbor::Tstr("tee"));
+  result->canonicalize();
+  return result;
+}
+
+std::pair<std::vector<uint8_t> /* privKey */, cppbor::Array /* BCC */>
+TpmRemoteProvisioningContext::GenerateBcc(bool testMode) const {
+  std::vector<uint8_t> privKey(ED25519_PRIVATE_KEY_LEN);
+  std::vector<uint8_t> pubKey(ED25519_PUBLIC_KEY_LEN);
+
+  std::vector<uint8_t> seed;
+  if (testMode) {
+    // Length is hard-coded in the BoringCrypto API without a constant
+    seed.resize(32);
+    RAND_bytes(seed.data(), seed.size());
+  } else {
+    // TODO: Switch to P256 signing keys that are TPM-bound.
+    seed = DeriveBytesFromHbk("BccKey", 32);
+  }
+  ED25519_keypair_from_seed(pubKey.data(), privKey.data(), seed.data());
+
+  auto coseKey = cppbor::Map()
+                     .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
+                     .add(CoseKey::ALGORITHM, EDDSA)
+                     .add(CoseKey::CURVE, ED25519)
+                     .add(CoseKey::KEY_OPS, VERIFY)
+                     .add(CoseKey::PUBKEY_X, pubKey)
+                     .canonicalize();
+  auto sign1Payload =
+      cppbor::Map()
+          .add(1 /* Issuer */, "Issuer")
+          .add(2 /* Subject */, "Subject")
+          .add(-4670552 /* Subject Pub Key */, coseKey.encode())
+          .add(-4670553 /* Key Usage (little-endian order) */,
+               std::vector<uint8_t>{0x20} /* keyCertSign = 1<<5 */)
+          .canonicalize()
+          .encode();
+  auto coseSign1 = constructCoseSign1(privKey,       /* signing key */
+                                      cppbor::Map(), /* extra protected */
+                                      sign1Payload, {} /* AAD */);
+  assert(coseSign1);
+
+  return {privKey,
+          cppbor::Array().add(std::move(coseKey)).add(coseSign1.moveValue())};
+}
+
+void TpmRemoteProvisioningContext::SetSystemVersion(uint32_t os_version,
+                                                    uint32_t os_patchlevel) {
+  os_version_ = os_version;
+  os_patchlevel_ = os_patchlevel;
+}
+
+void TpmRemoteProvisioningContext::SetVendorPatchlevel(
+    uint32_t vendor_patchlevel) {
+  vendor_patchlevel_ = vendor_patchlevel;
+}
+
+void TpmRemoteProvisioningContext::SetBootPatchlevel(uint32_t boot_patchlevel) {
+  boot_patchlevel_ = boot_patchlevel;
+}
+
+void TpmRemoteProvisioningContext::SetVerifiedBootInfo(
+    std::string_view boot_state, std::string_view bootloader_state,
+    const std::vector<uint8_t>& vbmeta_digest) {
+  verified_boot_state_ = boot_state;
+  bootloader_state_ = bootloader_state;
+  vbmeta_digest_ = vbmeta_digest;
+}
+
+ErrMsgOr<std::vector<uint8_t>>
+TpmRemoteProvisioningContext::BuildProtectedDataPayload(
+    bool isTestMode,                     //
+    const std::vector<uint8_t>& macKey,  //
+    const std::vector<uint8_t>& aad) const {
+  std::vector<uint8_t> devicePrivKey;
+  cppbor::Array bcc;
+  if (isTestMode) {
+    std::tie(devicePrivKey, bcc) = GenerateBcc(/*testMode=*/true);
+  } else {
+    devicePrivKey = devicePrivKey_;
+    auto clone = bcc_.clone();
+    if (!clone->asArray()) {
+      return "The BCC is not an array";
+    }
+    bcc = std::move(*clone->asArray());
+  }
+  auto sign1 = constructCoseSign1(devicePrivKey, macKey, aad);
+  if (!sign1) {
+    return sign1.moveMessage();
+  }
+  return cppbor::Array().add(sign1.moveValue()).add(std::move(bcc)).encode();
+}
+
+std::optional<cppcose::HmacSha256>
+TpmRemoteProvisioningContext::GenerateHmacSha256(
+    const cppcose::bytevec& input) const {
+  auto signing_key_builder = PrimaryKeyBuilder();
+  signing_key_builder.SigningKey();
+  signing_key_builder.UniqueData("Public Key Authentication Key");
+  auto signing_key = signing_key_builder.CreateKey(resource_manager_);
+  if (!signing_key) {
+    LOG(ERROR) << "Could not make MAC key for authenticating the pubkey";
+    return std::nullopt;
+  }
+
+  auto tpm_digest =
+      TpmHmac(resource_manager_, signing_key->get(), TpmAuth(ESYS_TR_PASSWORD),
+              input.data(), input.size());
+
+  if (!tpm_digest) {
+    LOG(ERROR) << "Could not calculate hmac";
+    return std::nullopt;
+  }
+
+  cppcose::HmacSha256 hmac;
+  if (tpm_digest->size != hmac.size()) {
+    LOG(ERROR) << "TPM-generated digest was too short. Actual size: "
+               << tpm_digest->size << " expected " << hmac.size() << " bytes";
+    return std::nullopt;
+  }
+
+  std::copy(tpm_digest->buffer, tpm_digest->buffer + tpm_digest->size,
+            hmac.begin());
+  return hmac;
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_remote_provisioning_context.h b/host/commands/secure_env/tpm_remote_provisioning_context.h
new file mode 100644
index 0000000..9c9e359
--- /dev/null
+++ b/host/commands/secure_env/tpm_remote_provisioning_context.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <keymaster/remote_provisioning_context.h>
+
+#include "host/commands/secure_env/tpm_resource_manager.h"
+#include "keymaster/cppcose/cppcose.h"
+
+namespace cuttlefish {
+
+/**
+ * TPM-backed implementation of the provisioning context.
+ */
+class TpmRemoteProvisioningContext
+    : public keymaster::RemoteProvisioningContext {
+ public:
+  TpmRemoteProvisioningContext(TpmResourceManager& resource_manager);
+  ~TpmRemoteProvisioningContext() override = default;
+  std::vector<uint8_t> DeriveBytesFromHbk(const std::string& context,
+                                          size_t numBytes) const override;
+  std::unique_ptr<cppbor::Map> CreateDeviceInfo() const override;
+  cppcose::ErrMsgOr<std::vector<uint8_t>> BuildProtectedDataPayload(
+      bool isTestMode,                     //
+      const std::vector<uint8_t>& macKey,  //
+      const std::vector<uint8_t>& aad) const override;
+  std::optional<cppcose::HmacSha256> GenerateHmacSha256(
+      const cppcose::bytevec& input) const override;
+  std::pair<std::vector<uint8_t>, cppbor::Array> GenerateBcc(
+      bool testMode) const;
+  void SetSystemVersion(uint32_t os_version, uint32_t os_patchlevel);
+  void SetVendorPatchlevel(uint32_t vendor_patchlevel);
+  void SetBootPatchlevel(uint32_t boot_patchlevel);
+  void SetVerifiedBootInfo(std::string_view boot_state,
+                           std::string_view bootloader_state,
+                           const std::vector<uint8_t>& vbmeta_digest);
+
+ private:
+  std::vector<uint8_t> devicePrivKey_;
+  cppbor::Array bcc_;
+  TpmResourceManager& resource_manager_;
+
+  std::optional<uint32_t> os_version_;
+  std::optional<uint32_t> os_patchlevel_;
+  std::optional<uint32_t> vendor_patchlevel_;
+  std::optional<uint32_t> boot_patchlevel_;
+  std::optional<std::string> verified_boot_state_;
+  std::optional<std::string> bootloader_state_;
+  std::optional<std::vector<uint8_t>> vbmeta_digest_;
+};
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_resource_manager.cpp b/host/commands/secure_env/tpm_resource_manager.cpp
index 3651ec8..defe153 100644
--- a/host/commands/secure_env/tpm_resource_manager.cpp
+++ b/host/commands/secure_env/tpm_resource_manager.cpp
@@ -18,6 +18,8 @@
 #include <android-base/logging.h>
 #include <tss2/tss2_rc.h>
 
+namespace cuttlefish {
+
 TpmResourceManager::ObjectSlot::ObjectSlot(TpmResourceManager* resource_manager)
     : ObjectSlot(resource_manager, ESYS_TR_NONE) {
 }
@@ -75,3 +77,5 @@
   }
   return TpmObjectSlot{new ObjectSlot(this)};
 }
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_resource_manager.h b/host/commands/secure_env/tpm_resource_manager.h
index e1ed83f..d4ce2f2 100644
--- a/host/commands/secure_env/tpm_resource_manager.h
+++ b/host/commands/secure_env/tpm_resource_manager.h
@@ -21,6 +21,8 @@
 
 #include <tss2/tss2_esys.h>
 
+namespace cuttlefish {
+
 /**
  * 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
@@ -60,3 +62,5 @@
 };
 
 using TpmObjectSlot = std::shared_ptr<TpmResourceManager::ObjectSlot>;
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_serialize.cpp b/host/commands/secure_env/tpm_serialize.cpp
index 1cf07ff..5e3fc8c 100644
--- a/host/commands/secure_env/tpm_serialize.cpp
+++ b/host/commands/secure_env/tpm_serialize.cpp
@@ -21,6 +21,8 @@
 #include "tss2/tss2_mu.h"
 #include "tss2/tss2_rc.h"
 
+namespace cuttlefish {
+
 template<typename T>
 int MarshalFn = 0; // Break code without an explicit specialization.
 
@@ -82,3 +84,5 @@
 
 template class TpmSerializable<TPM2B_PRIVATE>;
 template class TpmSerializable<TPM2B_PUBLIC>;
+
+}  // namespace cuttlefish
diff --git a/host/commands/secure_env/tpm_serialize.h b/host/commands/secure_env/tpm_serialize.h
index 17884a5..4043ea3 100644
--- a/host/commands/secure_env/tpm_serialize.h
+++ b/host/commands/secure_env/tpm_serialize.h
@@ -24,6 +24,8 @@
 
 #include <android-base/logging.h>
 
+namespace cuttlefish {
+
 /**
  * An implementation of a keymaster::Serializable type that refers to a TPM type
  * by an unmanaged pointer. When the TpmSerializable serializes or deserializes
@@ -50,3 +52,5 @@
 
 using SerializeTpmKeyPrivate = TpmSerializable<TPM2B_PRIVATE>;
 using SerializeTpmKeyPublic = TpmSerializable<TPM2B_PUBLIC>;
+
+}  // namespace cuttlefish
diff --git a/host/commands/launch/Android.bp b/host/commands/start/Android.bp
similarity index 87%
rename from host/commands/launch/Android.bp
rename to host/commands/start/Android.bp
index 6adf7a2..289edc3 100644
--- a/host/commands/launch/Android.bp
+++ b/host/commands/start/Android.bp
@@ -18,17 +18,20 @@
 }
 
 cc_binary {
-    name: "launch_cvd",
+    name: "cvd_internal_start",
+    symlinks: ["launch_cvd"],
     srcs: [
         "filesystem_explorer.cc",
         "flag_forwarder.cc",
-        "launch_cvd.cc",
+        "main.cc",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libbase",
         "libjsoncpp",
+        "libfruit",
         "libnl",
         "libxml2",
         "libz",
@@ -39,7 +42,8 @@
         "libgflags",
     ],
     required: [
-        "mkenvimage",
+        "assemble_cvd",
+        "run_cvd",
     ],
     defaults: ["cuttlefish_host", "cuttlefish_libicuuc"],
 }
diff --git a/host/commands/launch/filesystem_explorer.cc b/host/commands/start/filesystem_explorer.cc
similarity index 100%
rename from host/commands/launch/filesystem_explorer.cc
rename to host/commands/start/filesystem_explorer.cc
diff --git a/host/commands/launch/filesystem_explorer.h b/host/commands/start/filesystem_explorer.h
similarity index 100%
rename from host/commands/launch/filesystem_explorer.h
rename to host/commands/start/filesystem_explorer.h
diff --git a/host/commands/launch/flag_forwarder.cc b/host/commands/start/flag_forwarder.cc
similarity index 99%
rename from host/commands/launch/flag_forwarder.cc
rename to host/commands/start/flag_forwarder.cc
index 692e453..aa492c5 100644
--- a/host/commands/launch/flag_forwarder.cc
+++ b/host/commands/start/flag_forwarder.cc
@@ -290,8 +290,7 @@
     // Ensure this is set on by putting it at the end.
     cmd.AddParameter("--helpxml");
     std::string helpxml_input, helpxml_output, helpxml_error;
-    cuttlefish::SubprocessOptions options;
-    options.Verbose(false);
+    auto options = cuttlefish::SubprocessOptions().Verbose(false);
     int helpxml_ret = cuttlefish::RunWithManagedStdio(std::move(cmd), &helpxml_input,
                                                &helpxml_output, &helpxml_error,
                                                options);
diff --git a/host/commands/launch/flag_forwarder.h b/host/commands/start/flag_forwarder.h
similarity index 100%
rename from host/commands/launch/flag_forwarder.h
rename to host/commands/start/flag_forwarder.h
diff --git a/host/commands/launch/launch_cvd.cc b/host/commands/start/main.cc
similarity index 98%
rename from host/commands/launch/launch_cvd.cc
rename to host/commands/start/main.cc
index f67ce48..c11ac67 100644
--- a/host/commands/launch/launch_cvd.cc
+++ b/host/commands/start/main.cc
@@ -23,13 +23,12 @@
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/subprocess.h"
-#include "host/commands/launch/filesystem_explorer.h"
+#include "host/commands/start/filesystem_explorer.h"
+#include "host/commands/start/flag_forwarder.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/host_tools_version.h"
 #include "host/libs/config/fetcher_config.h"
 
-#include "flag_forwarder.h"
-
 /**
  * If stdin is a tty, that means a user is invoking launch_cvd on the command
  * line and wants automatic file detection for assemble_cvd.
diff --git a/host/commands/stop_cvd/Android.bp b/host/commands/status/Android.bp
similarity index 90%
copy from host/commands/stop_cvd/Android.bp
copy to host/commands/status/Android.bp
index a670a25..4ce0e8c 100644
--- a/host/commands/stop_cvd/Android.bp
+++ b/host/commands/status/Android.bp
@@ -18,15 +18,17 @@
 }
 
 cc_binary {
-    name: "stop_cvd",
+    name: "cvd_internal_status",
+    symlinks: ["cvd_status"],
     srcs: [
         "main.cc",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
-        "libcuttlefish_allocd_utils",
+        "libfruit",
         "libjsoncpp",
     ],
     static_libs: [
diff --git a/host/commands/status/main.cc b/host/commands/status/main.cc
new file mode 100644
index 0000000..a479081
--- /dev/null
+++ b/host/commands/status/main.cc
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/environment.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/tee_logging.h"
+#include "host/commands/run_cvd/runner_defs.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/vm_manager/vm_manager.h"
+
+#define CHECK_PRINT(print, condition, message)                               \
+  if (print) {                                                               \
+    if (!(condition)) {                                                      \
+      std::cout << "      Status: Stopped (" << message << ")" << std::endl; \
+      exit(0);                                                               \
+    }                                                                        \
+  } else                                                                     \
+    CHECK(condition) << message
+
+namespace cuttlefish {
+
+int CvdStatusMain(int argc, char** argv) {
+  ::android::base::InitLogging(argv, android::base::StderrLogger);
+  ::android::base::SetLogger(LogToStderrAndFiles({}));
+
+  std::vector<Flag> flags;
+
+  std::int32_t wait_for_launcher;
+  Json::Value device_info;
+  flags.emplace_back(
+      GflagsCompatFlag("wait_for_launcher", wait_for_launcher)
+          .Help("How many seconds to wait for the launcher to respond to the "
+                "status command. A value of zero means wait indefinitely"));
+  std::string instance_name;
+  flags.emplace_back(GflagsCompatFlag("instance_name", instance_name)
+                         .Help("Name of the instance to check. If not "
+                               "provided, DefaultInstance is used."));
+  bool print;
+  flags.emplace_back(GflagsCompatFlag("print", print)
+                         .Help("If provided, prints status and instance config "
+                               "information to stdout instead of CHECK"));
+  bool all_instances;
+  flags.emplace_back(
+      GflagsCompatFlag("all_instances", all_instances)
+          .Help("List all instances status and instance config information."));
+
+  flags.emplace_back(HelpFlag(flags));
+  flags.emplace_back(UnexpectedArgumentGuard());
+
+  std::vector<std::string> args =
+      ArgsToVec(argc - 1, argv + 1);  // Skip argv[0]
+  CHECK(ParseFlags(flags, args)) << "Could not process command line flags.";
+
+  auto config = CuttlefishConfig::Get();
+  CHECK(config) << "Failed to obtain config object";
+
+  auto instance_names = all_instances ? config->instance_names()
+                                      : std::vector<std::string>{instance_name};
+
+  Json::Value devices_info;
+  for (int index = 0; index < instance_names.size(); index++) {
+    std::string instance_name = instance_names[index];
+    auto instance = instance_name.empty()
+                        ? config->ForDefaultInstance()
+                        : config->ForInstanceName(instance_name);
+    auto monitor_path = instance.launcher_monitor_socket_path();
+    CHECK_PRINT(print, !monitor_path.empty(),
+                "No path to launcher monitor found");
+
+    auto monitor_socket = SharedFD::SocketLocalClient(
+        monitor_path.c_str(), false, SOCK_STREAM, wait_for_launcher);
+    CHECK_PRINT(print, monitor_socket->IsOpen(),
+                "Unable to connect to launcher monitor at " + monitor_path +
+                    ": " + monitor_socket->StrError());
+
+    auto request = LauncherAction::kStatus;
+    auto bytes_sent = monitor_socket->Send(&request, sizeof(request), 0);
+    CHECK_PRINT(print, bytes_sent > 0,
+                "Error sending launcher monitor the status command: " +
+                    monitor_socket->StrError());
+
+    // Perform a select with a timeout to guard against launcher hanging
+    SharedFDSet read_set;
+    read_set.Set(monitor_socket);
+    struct timeval timeout = {wait_for_launcher, 0};
+    int selected = Select(&read_set, nullptr, nullptr,
+                          wait_for_launcher <= 0 ? nullptr : &timeout);
+    CHECK_PRINT(
+        print, selected >= 0,
+        std::string("Failed communication with the launcher monitor: ") +
+            strerror(errno));
+    CHECK_PRINT(print, selected > 0,
+                "Timeout expired waiting for launcher monitor to respond");
+
+    LauncherResponse response;
+    auto bytes_recv = monitor_socket->Recv(&response, sizeof(response), 0);
+    CHECK_PRINT(
+        print, bytes_recv > 0,
+        std::string("Error receiving response from launcher monitor: ") +
+            monitor_socket->StrError());
+    CHECK_PRINT(print, response == LauncherResponse::kSuccess,
+                std::string("Received '") + static_cast<char>(response) +
+                    "' response from launcher monitor");
+    if (print) {
+      devices_info[index]["assembly_dir"] = config->assembly_dir();
+      devices_info[index]["instance_name"] = instance.instance_name();
+      devices_info[index]["instance_dir"] = instance.instance_dir();
+      devices_info[index]["web_access"] =
+          "https://" + config->sig_server_address() + ":" +
+          std::to_string(config->sig_server_port()) +
+          "/client.html?deviceId=" + instance.instance_name();
+      devices_info[index]["adb_serial"] = instance.adb_ip_and_port();
+      devices_info[index]["webrtc_port"] =
+          std::to_string(config->sig_server_port());
+      for (int i = 0; i < config->display_configs().size(); i++) {
+        devices_info[index]["displays"][i] =
+            std::to_string(config->display_configs()[i].width) + " x " +
+            std::to_string(config->display_configs()[i].height) + " ( " +
+            std::to_string(config->display_configs()[i].dpi) + " )";
+      }
+      devices_info[index]["status"] = "Running";
+      if (index == (instance_names.size() - 1)) {
+        std::cout << devices_info.toStyledString() << std::endl;
+      }
+    } else {
+      LOG(INFO) << "run_cvd is active.";
+    }
+  }
+  return 0;
+}
+}  // namespace cuttlefish
+
+int main(int argc, char** argv) {
+  return cuttlefish::CvdStatusMain(argc, argv);
+}
diff --git a/host/commands/stop_cvd/Android.bp b/host/commands/stop/Android.bp
similarity index 91%
rename from host/commands/stop_cvd/Android.bp
rename to host/commands/stop/Android.bp
index a670a25..7d705fd 100644
--- a/host/commands/stop_cvd/Android.bp
+++ b/host/commands/stop/Android.bp
@@ -18,15 +18,18 @@
 }
 
 cc_binary {
-    name: "stop_cvd",
+    name: "cvd_internal_stop",
+    symlinks: ["stop_cvd"],
     srcs: [
         "main.cc",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libcuttlefish_allocd_utils",
+        "libfruit",
         "libjsoncpp",
     ],
     static_libs: [
diff --git a/host/commands/stop_cvd/main.cc b/host/commands/stop/main.cc
similarity index 61%
rename from host/commands/stop_cvd/main.cc
rename to host/commands/stop/main.cc
index 9e4fdd4..44ed5ec 100644
--- a/host/commands/stop_cvd/main.cc
+++ b/host/commands/stop/main.cc
@@ -37,30 +37,27 @@
 #include <vector>
 
 #include <android-base/strings.h>
-#include <gflags/gflags.h>
 #include <android-base/logging.h>
 
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/fs/shared_select.h"
 #include "common/libs/utils/environment.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/result.h"
 #include "host/commands/run_cvd/runner_defs.h"
 #include "host/libs/allocd/request.h"
 #include "host/libs/allocd/utils.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/vm_manager/vm_manager.h"
 
-DEFINE_int32(wait_for_launcher, 5,
-             "How many seconds to wait for the launcher to respond to the stop "
-             "command. A value of zero means wait indefinetly");
-
 namespace cuttlefish {
 namespace {
 
-std::set<std::string> FallbackPaths() {
+std::set<std::string> FallbackDirs() {
   std::set<std::string> paths;
   std::string parent_path = StringFromEnv("HOME", ".");
   paths.insert(parent_path + "/cuttlefish_assembly");
-  paths.insert(parent_path + "/cuttlefish_assembly/*");
 
   std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(parent_path.c_str()), closedir);
   for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
@@ -68,43 +65,26 @@
     if (!android::base::StartsWith(subdir, "cuttlefish_runtime.")) {
       continue;
     }
-    auto instance_dir = parent_path + "/" + subdir;
-    // Add the instance directory
-    paths.insert(instance_dir);
-    // Add files in instance dir
-    paths.insert(instance_dir + "/*");
-    // Add files in the tombstone directory
-    paths.insert(instance_dir + "/tombstones/*");
-    // Add files in the internal directory
-    paths.insert(instance_dir + "/" + std::string(kInternalDirName) + "/*");
-    // Add files in the shared directory
-    paths.insert(instance_dir + "/" + std::string(kSharedDirName) + "/*");
+    paths.insert(parent_path + "/" + subdir);
   }
   return paths;
 }
 
-std::set<std::string> PathsForInstance(const CuttlefishConfig& config,
-                                       const CuttlefishConfig::InstanceSpecific instance) {
+std::set<std::string> DirsForInstance(
+    const CuttlefishConfig& config,
+    const CuttlefishConfig::InstanceSpecific instance) {
   return {
-    config.assembly_dir(),
-    config.assembly_dir() + "/*",
-    instance.instance_dir(),
-    instance.PerInstancePath("*"),
-    instance.PerInstancePath("tombstones"),
-    instance.PerInstancePath("tombstones/*"),
-    instance.instance_internal_dir(),
-    instance.PerInstanceInternalPath("*"),
-    instance.PerInstancePath(kSharedDirName),
-    instance.PerInstancePath(kSharedDirName) + "/*",
+      config.assembly_dir(),
+      instance.instance_dir(),
   };
 }
 
 // Gets a set of the possible process groups of a previous launch
-std::set<pid_t> GetCandidateProcessGroups(const std::set<std::string>& paths) {
+std::set<pid_t> GetCandidateProcessGroups(const std::set<std::string>& dirs) {
   std::stringstream cmd;
   cmd << "lsof -t 2>/dev/null";
-  for (const auto& path : paths) {
-    cmd << " " << path;
+  for (const auto& dir : dirs) {
+    cmd << " +D " << dir;
   }
   std::string cmd_str = cmd.str();
   std::shared_ptr<FILE> cmd_out(popen(cmd_str.c_str(), "r"), pclose);
@@ -128,10 +108,10 @@
   return ret;
 }
 
-int FallBackStop(const std::set<std::string>& paths) {
+int FallBackStop(const std::set<std::string>& dirs) {
   auto exit_code = 1; // Having to fallback is an error
 
-  auto process_groups = GetCandidateProcessGroups(paths);
+  auto process_groups = GetCandidateProcessGroups(dirs);
   for (auto pgid: process_groups) {
     LOG(INFO) << "Sending SIGKILL to process group " << pgid;
     auto retval = killpg(pgid, SIGKILL);
@@ -145,62 +125,52 @@
   return exit_code;
 }
 
-bool CleanStopInstance(const CuttlefishConfig::InstanceSpecific& instance) {
+Result<void> CleanStopInstance(
+    const CuttlefishConfig::InstanceSpecific& instance,
+    std::int32_t wait_for_launcher) {
   auto monitor_path = instance.launcher_monitor_socket_path();
-  if (monitor_path.empty()) {
-    LOG(ERROR) << "No path to launcher monitor found";
-    return false;
-  }
+  CF_EXPECT(!monitor_path.empty(), "No path to launcher monitor found");
+
   auto monitor_socket = SharedFD::SocketLocalClient(
-      monitor_path.c_str(), false, SOCK_STREAM, FLAGS_wait_for_launcher);
-  if (!monitor_socket->IsOpen()) {
-    LOG(ERROR) << "Unable to connect to launcher monitor at " << monitor_path
-               << ": " << monitor_socket->StrError();
-    return false;
-  }
+      monitor_path.c_str(), false, SOCK_STREAM, wait_for_launcher);
+  CF_EXPECT(monitor_socket->IsOpen(),
+            "Unable to connect to launcher monitor at "
+                << monitor_path << ": " << monitor_socket->StrError());
+
   auto request = LauncherAction::kStop;
   auto bytes_sent = monitor_socket->Send(&request, sizeof(request), 0);
-  if (bytes_sent < 0) {
-    LOG(ERROR) << "Error sending launcher monitor the stop command: "
-               << monitor_socket->StrError();
-    return false;
-  }
+  CF_EXPECT(bytes_sent >= 0, "Error sending launcher monitor the stop command: "
+                                 << monitor_socket->StrError());
+
   // Perform a select with a timeout to guard against launcher hanging
   SharedFDSet read_set;
   read_set.Set(monitor_socket);
-  struct timeval timeout = {FLAGS_wait_for_launcher, 0};
+  struct timeval timeout = {wait_for_launcher, 0};
   int selected = Select(&read_set, nullptr, nullptr,
-                        FLAGS_wait_for_launcher <= 0 ? nullptr : &timeout);
-  if (selected < 0){
-    LOG(ERROR) << "Failed communication with the launcher monitor: "
-               << strerror(errno);
-    return false;
-  }
-  if (selected == 0) {
-    LOG(ERROR) << "Timeout expired waiting for launcher monitor to respond";
-    return false;
-  }
+                        wait_for_launcher <= 0 ? nullptr : &timeout);
+  CF_EXPECT(selected >= 0, "Failed communication with the launcher monitor: "
+                               << strerror(errno));
+  CF_EXPECT(selected > 0, "Timeout expired waiting for launcher to respond");
+
   LauncherResponse response;
   auto bytes_recv = monitor_socket->Recv(&response, sizeof(response), 0);
-  if (bytes_recv < 0) {
-    LOG(ERROR) << "Error receiving response from launcher monitor: "
-               << monitor_socket->StrError();
-    return false;
-  }
-  if (response != LauncherResponse::kSuccess) {
-    LOG(ERROR) << "Received '" << static_cast<char>(response)
-               << "' response from launcher monitor";
-    return false;
-  }
-  LOG(INFO) << "Successfully stopped device " << instance.adb_ip_and_port();
-  return true;
+  CF_EXPECT(bytes_recv >= 0, "Error receiving response from launcher monitor: "
+                                 << monitor_socket->StrError());
+  CF_EXPECT(response == LauncherResponse::kSuccess,
+            "Received '" << static_cast<char>(response)
+                         << "' response from launcher monitor");
+  LOG(INFO) << "Successfully stopped device " << instance.instance_name()
+            << ": " << instance.adb_ip_and_port();
+  return {};
 }
 
 int StopInstance(const CuttlefishConfig& config,
-                 const CuttlefishConfig::InstanceSpecific& instance) {
-  bool res = CleanStopInstance(instance);
-  if (!res) {
-    return FallBackStop(PathsForInstance(config, instance));
+                 const CuttlefishConfig::InstanceSpecific& instance,
+                 std::int32_t wait_for_launcher) {
+  auto res = CleanStopInstance(instance, wait_for_launcher);
+  if (!res.ok()) {
+    LOG(ERROR) << "Clean stop failed: " << res.error();
+    return FallBackStop(DirsForInstance(config, instance));
   }
   return 0;
 }
@@ -230,18 +200,35 @@
 
 int StopCvdMain(int argc, char** argv) {
   ::android::base::InitLogging(argv, android::base::StderrLogger);
-  google::ParseCommandLineFlags(&argc, &argv, true);
+
+  std::vector<Flag> flags;
+
+  std::int32_t wait_for_launcher = 5;
+  flags.emplace_back(
+      GflagsCompatFlag("wait_for_launcher", wait_for_launcher)
+          .Help("How many seconds to wait for the launcher to respond to the "
+                "status command. A value of zero means wait indefinitely"));
+  bool clear_instance_dirs;
+  flags.emplace_back(
+      GflagsCompatFlag("clear_instance_dirs", clear_instance_dirs)
+          .Help("If provided, deletes the instance dir after attempting to "
+                "stop each instance."));
+  flags.emplace_back(HelpFlag(flags));
+  flags.emplace_back(UnexpectedArgumentGuard());
+  std::vector<std::string> args =
+      ArgsToVec(argc - 1, argv + 1);  // Skip argv[0]
+  CHECK(ParseFlags(flags, args)) << "Could not process command line flags.";
 
   auto config = CuttlefishConfig::Get();
   if (!config) {
     LOG(ERROR) << "Failed to obtain config object";
-    return FallBackStop(FallbackPaths());
+    return FallBackStop(FallbackDirs());
   }
 
   int ret = 0;
   for (const auto& instance : config->Instances()) {
     auto session_id = instance.session_id();
-    int exit_status = StopInstance(*config, instance);
+    int exit_status = StopInstance(*config, instance, wait_for_launcher);
     if (exit_status == 0 && instance.use_allocd()) {
       // only release session resources if the instance was stopped
       SharedFD allocd_sock =
@@ -254,6 +241,14 @@
 
       ReleaseAllocdResources(allocd_sock, session_id);
     }
+    if (clear_instance_dirs) {
+      if (DirectoryExists(instance.instance_dir())) {
+        LOG(INFO) << "Deleting instance dir " << instance.instance_dir();
+        if (!RecursivelyRemoveDirectory(instance.instance_dir())) {
+          LOG(ERROR) << "Unable to rmdir " << instance.instance_dir();
+        }
+      }
+    }
     ret |= exit_status;
   }
 
diff --git a/host/commands/tapsetiff/tapsetiff.py b/host/commands/tapsetiff/tapsetiff.py
deleted file mode 100755
index 8a0999e..0000000
--- a/host/commands/tapsetiff/tapsetiff.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/python3
-
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http:#www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import fcntl
-import struct
-import sys
-
-TUNSETIFF = 0x400454ca
-IFF_TAP = 0x0002
-IFF_NO_PI = 0x1000
-IFF_VNET_HDR = 0x4000
-
-tun_fd = int(sys.argv[1])
-tap_name = sys.argv[2]
-
-ifr = struct.pack('16sH', tap_name.encode('utf-8'), IFF_TAP | IFF_NO_PI | IFF_VNET_HDR)
-fcntl.ioctl(tun_fd, TUNSETIFF, ifr)
diff --git a/host/commands/test_gce_driver/Android.bp b/host/commands/test_gce_driver/Android.bp
new file mode 100644
index 0000000..c59f582
--- /dev/null
+++ b/host/commands/test_gce_driver/Android.bp
@@ -0,0 +1,87 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "cvd_test_gce_driver",
+    srcs: [
+        "cvd_test_gce_driver.cpp",
+        "gce_api.cpp",
+        "key_pair.cpp",
+        "scoped_instance.cpp",
+    ],
+    static_libs: [
+        "libcuttlefish_web",
+        "libcuttlefish_host_config",
+        "libcuttlefish_test_gce_proto_cpp",
+        "libgflags",
+        "libext2_blkid",
+    ],
+    target: {
+        host: {
+            stl: "libc++_static",
+            static_libs: [
+                "libbase",
+                "libcuttlefish_fs",
+                "libcuttlefish_utils",
+                "libcurl",
+                "libcrypto",
+                "libext2_uuid",
+                "liblog",
+                "libssl",
+                "libz",
+                "libjsoncpp",
+                "libprotobuf-cpp-full",
+            ],
+        },
+        android: {
+            shared_libs: [
+                "libbase",
+                "libcuttlefish_fs",
+                "libcuttlefish_utils",
+                "libcurl",
+                "libcrypto",
+                "libext2_uuid",
+                "liblog",
+                "libssl",
+                "libz",
+                "libjsoncpp",
+                "libprotobuf-cpp-full",
+            ],
+        },
+    },
+    defaults: ["cuttlefish_host"],
+}
+
+cc_library_static {
+    name: "libcuttlefish_test_gce_proto_cpp",
+    proto: {
+        export_proto_headers: true,
+        type: "full",
+    },
+    srcs: ["test_gce_driver.proto"],
+    defaults: ["cuttlefish_host"],
+}
+
+java_library_host {
+    name: "libcuttlefish_test_gce_proto_java",
+    proto: {
+        type: "full",
+    },
+    srcs: ["test_gce_driver.proto"],
+}
diff --git a/host/commands/test_gce_driver/cvd_test_gce_driver.cpp b/host/commands/test_gce_driver/cvd_test_gce_driver.cpp
new file mode 100644
index 0000000..d037478
--- /dev/null
+++ b/host/commands/test_gce_driver/cvd_test_gce_driver.cpp
@@ -0,0 +1,410 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <curl/curl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <iterator>
+#include <optional>
+#include <string>
+#include <unordered_map>
+
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <android-base/strings.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+#include <google/protobuf/text_format.h>
+#include <google/protobuf/util/delimited_message_util.h>
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/archive.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/test_gce_driver/gce_api.h"
+#include "host/commands/test_gce_driver/key_pair.h"
+#include "host/commands/test_gce_driver/scoped_instance.h"
+#include "host/libs/web/build_api.h"
+#include "host/libs/web/credential_source.h"
+#include "host/libs/web/curl_wrapper.h"
+#include "host/libs/web/install_zip.h"
+
+#include "test_gce_driver.pb.h"
+
+using android::base::Error;
+using android::base::Result;
+
+using google::protobuf::util::ParseDelimitedFromZeroCopyStream;
+using google::protobuf::util::SerializeDelimitedToFileDescriptor;
+
+namespace cuttlefish {
+namespace {
+
+Result<Json::Value> ReadJsonFromFile(const std::string& path) {
+  Json::CharReaderBuilder builder;
+  std::ifstream ifs(path);
+  Json::Value content;
+  std::string errorMessage;
+  CF_EXPECT(Json::parseFromStream(builder, ifs, &content, &errorMessage),
+            "Could not read config file \"" << path << "\": " << errorMessage);
+  return content;
+}
+
+class ReadEvalPrintLoop {
+ public:
+  ReadEvalPrintLoop(GceApi& gce, BuildApi& build, int in_fd, int out_fd,
+                    bool internal_addresses)
+      : gce_(gce),
+        build_(build),
+        in_(in_fd),
+        out_(out_fd),
+        internal_addresses_(internal_addresses) {}
+
+  Result<void> Process() {
+    while (true) {
+      test_gce_driver::TestMessage msg;
+      bool clean_eof;
+      LOG(DEBUG) << "Waiting for message";
+      bool parsed = ParseDelimitedFromZeroCopyStream(&msg, &in_, &clean_eof);
+      LOG(DEBUG) << "Received message";
+      if (clean_eof) {
+        return {};
+      } else if (!parsed) {
+        return Error() << "Failed to parse input message";
+      }
+      Result<void> handler_result;
+      switch (msg.contents_case()) {
+        case test_gce_driver::TestMessage::ContentsCase::kExit: {
+          test_gce_driver::TestMessage stream_end_msg;
+          stream_end_msg.mutable_exit();  // Set this in the oneof
+          if (!SerializeDelimitedToFileDescriptor(stream_end_msg, out_)) {
+            return Error() << "Failure while writing stream end message";
+          }
+          return {};
+        }
+        case test_gce_driver::TestMessage::ContentsCase::kStreamEnd:
+          continue;
+        case test_gce_driver::TestMessage::ContentsCase::kCreateInstance:
+          handler_result = NewInstance(msg.create_instance());
+          break;
+        case test_gce_driver::TestMessage::ContentsCase::kSshCommand:
+          handler_result = SshCommand(msg.ssh_command());
+          break;
+        case test_gce_driver::TestMessage::ContentsCase::kUploadBuildArtifact:
+          handler_result = UploadBuildArtifact(msg.upload_build_artifact());
+          break;
+        case test_gce_driver::TestMessage::ContentsCase::kUploadFile:
+          handler_result = UploadFile(msg.upload_file());
+          break;
+        default: {
+          std::string msg_txt;
+          if (google::protobuf::TextFormat::PrintToString(msg, &msg_txt)) {
+            handler_result = Error()
+                             << "Unexpected message: \"" << msg_txt << "\"";
+          } else {
+            handler_result = Error() << "Unexpected message: (unprintable)";
+          }
+        }
+      }
+      if (!handler_result.ok()) {
+        test_gce_driver::TestMessage error_msg;
+        error_msg.mutable_error()->set_text(handler_result.error().message());
+        CF_EXPECT(SerializeDelimitedToFileDescriptor(error_msg, out_),
+                  "Failure while writing error message: (\n"
+                      << handler_result.error() << "\n)");
+      }
+      test_gce_driver::TestMessage stream_end_msg;
+      stream_end_msg.mutable_stream_end();  // Set this in the oneof
+      CF_EXPECT(SerializeDelimitedToFileDescriptor(stream_end_msg, out_));
+    }
+    return {};
+  }
+
+ private:
+  Result<void> NewInstance(const test_gce_driver::CreateInstance& request) {
+    CF_EXPECT(request.id().name() != "", "Instance name must be specified");
+    CF_EXPECT(request.id().zone() != "", "Instance zone must be specified");
+    auto instance = CF_EXPECT(ScopedGceInstance::CreateDefault(
+        gce_, request.id().zone(), request.id().name(), internal_addresses_));
+    instances_.emplace(request.id().name(), std::move(instance));
+    return {};
+  }
+  Result<void> SshCommand(const test_gce_driver::SshCommand& request) {
+    auto instance = instances_.find(request.instance().name());
+    CF_EXPECT(instance != instances_.end(),
+              "Instance \"" << request.instance().name() << "\" not found");
+    auto ssh = CF_EXPECT(instance->second->Ssh());
+    for (auto argument : request.arguments()) {
+      ssh.RemoteParameter(argument);
+    }
+
+    std::optional<Subprocess> ssh_proc;
+    SharedFD stdout_read;
+    SharedFD stderr_read;
+    {  // Things created here need to be closed early
+      auto cmd = ssh.Build();
+
+      SharedFD stdout_write;
+      CF_EXPECT(SharedFD::Pipe(&stdout_read, &stdout_write));
+      cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, stdout_write);
+
+      SharedFD stderr_write;
+      CF_EXPECT(SharedFD::Pipe(&stderr_read, &stderr_write));
+      cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, stderr_write);
+
+      ssh_proc = cmd.Start();
+    }
+
+    while (stdout_read->IsOpen() || stderr_read->IsOpen()) {
+      SharedFDSet read_set;
+      if (stdout_read->IsOpen()) {
+        read_set.Set(stdout_read);
+      }
+      if (stderr_read->IsOpen()) {
+        read_set.Set(stderr_read);
+      }
+      Select(&read_set, nullptr, nullptr, nullptr);
+      if (read_set.IsSet(stdout_read)) {
+        char buffer[1 << 14];
+        auto read = stdout_read->Read(buffer, sizeof(buffer));
+        CF_EXPECT(read >= 0,
+                  "Failure in reading ssh stdout: " << stdout_read->StrError());
+        if (read == 0) {  // EOF
+          stdout_read = SharedFD();
+        } else {
+          test_gce_driver::TestMessage stdout_chunk;
+          stdout_chunk.mutable_data()->set_type(
+              test_gce_driver::DataType::DATA_TYPE_STDOUT);
+          stdout_chunk.mutable_data()->set_contents(buffer, read);
+          CF_EXPECT(SerializeDelimitedToFileDescriptor(stdout_chunk, out_));
+        }
+      }
+      if (read_set.IsSet(stderr_read)) {
+        char buffer[1 << 14];
+        auto read = stderr_read->Read(buffer, sizeof(buffer));
+        CF_EXPECT(read >= 0,
+                  "Failure in reading ssh stderr: " << stdout_read->StrError());
+        if (read == 0) {  // EOF
+          stderr_read = SharedFD();
+        } else {
+          test_gce_driver::TestMessage stderr_chunk;
+          stderr_chunk.mutable_data()->set_type(
+              test_gce_driver::DataType::DATA_TYPE_STDERR);
+          stderr_chunk.mutable_data()->set_contents(buffer, read);
+          CF_EXPECT(SerializeDelimitedToFileDescriptor(stderr_chunk, out_));
+        }
+      }
+    }
+
+    auto ret = ssh_proc->Wait();
+    test_gce_driver::TestMessage retcode_chunk;
+    retcode_chunk.mutable_data()->set_type(
+        test_gce_driver::DataType::DATA_TYPE_RETURN_CODE);
+    retcode_chunk.mutable_data()->set_contents(std::to_string(ret));
+    CF_EXPECT(SerializeDelimitedToFileDescriptor(retcode_chunk, out_));
+    return {};
+  }
+  Result<void> UploadBuildArtifact(
+      const test_gce_driver::UploadBuildArtifact& request) {
+    auto instance = instances_.find(request.instance().name());
+    CF_EXPECT(instance != instances_.end(),
+              "Instance \"" << request.instance().name() << "\" not found");
+
+    struct {
+      ScopedGceInstance* instance;
+      SharedFD ssh_in;
+      std::optional<Subprocess> ssh_proc;
+      Result<void> result;
+    } callback_state;
+
+    callback_state.instance = instance->second.get();
+
+    auto callback = [&request, &callback_state](char* data, size_t size) {
+      if (data == nullptr) {
+        auto ssh = callback_state.instance->Ssh();
+        if (!ssh.ok()) {
+          callback_state.result = CF_ERR("ssh command failed\n" << ssh.error());
+          return false;
+        }
+
+        SharedFD ssh_stdin_out;
+        if (!SharedFD::Pipe(&ssh_stdin_out, &callback_state.ssh_in)) {
+          callback_state.result = CF_ERRNO("pipe failed");
+          return false;
+        }
+
+        ssh->RemoteParameter("cat >" + request.remote_path());
+        auto command = ssh->Build();
+        command.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, ssh_stdin_out);
+        callback_state.ssh_proc = command.Start();
+      } else if (WriteAll(callback_state.ssh_in, data, size) != size) {
+        callback_state.ssh_proc->Stop();
+        callback_state.result = CF_ERR("Failed to write contents\n"
+                                       << callback_state.ssh_in->StrError());
+        return false;
+      }
+      return true;
+    };
+
+    DeviceBuild build(request.build().id(), request.build().target());
+    CF_EXPECT(
+        build_.ArtifactToCallback(build, request.artifact_name(), callback),
+        "Failed to send file: (\n"
+            << (callback_state.result.ok()
+                    ? "Unknown failure"
+                    : callback_state.result.error().message() + "\n)"));
+
+    callback_state.ssh_in->Close();
+
+    if (callback_state.ssh_proc) {
+      auto ssh_ret = callback_state.ssh_proc->Wait();
+      CF_EXPECT(ssh_ret == 0, "SSH command failed with code: " << ssh_ret);
+    }
+
+    return {};
+  }
+  Result<void> UploadFile(const test_gce_driver::UploadFile& request) {
+    auto instance = instances_.find(request.instance().name());
+    CF_EXPECT(instance != instances_.end(),
+              "Instance \"" << request.instance().name() << "\" not found");
+
+    auto ssh = CF_EXPECT(instance->second->Ssh());
+
+    ssh.RemoteParameter("cat >" + request.remote_path());
+
+    auto command = ssh.Build();
+
+    SharedFD ssh_stdin_out, ssh_stdin_in;
+    CF_EXPECT(SharedFD::Pipe(&ssh_stdin_out, &ssh_stdin_in), strerror(errno));
+    command.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, ssh_stdin_out);
+
+    auto ssh_proc = command.Start();
+    ssh_stdin_out->Close();
+
+    {
+      Command unused = std::move(command);  // force deletion
+    }
+
+    while (true) {
+      LOG(INFO) << "upload data loop";
+      test_gce_driver::TestMessage data_msg;
+      bool clean_eof;
+      LOG(DEBUG) << "Waiting for message";
+      bool parsed =
+          ParseDelimitedFromZeroCopyStream(&data_msg, &in_, &clean_eof);
+      if (clean_eof) {
+        ssh_proc.Stop();
+        return CF_ERR("Received EOF");
+      } else if (!parsed) {
+        ssh_proc.Stop();
+        return CF_ERR("Failed to parse message");
+      } else if (data_msg.contents_case() ==
+                 test_gce_driver::TestMessage::ContentsCase::kStreamEnd) {
+        break;
+      } else if (data_msg.contents_case() !=
+                 test_gce_driver::TestMessage::ContentsCase::kData) {
+        ssh_proc.Stop();
+        return CF_ERR(
+            "Received wrong type of message: " << data_msg.contents_case());
+      } else if (data_msg.data().type() !=
+                 test_gce_driver::DataType::DATA_TYPE_FILE_CONTENTS) {
+        ssh_proc.Stop();
+        return CF_ERR(
+            "Received unexpected data type: " << data_msg.data().type());
+      }
+      LOG(INFO) << "going to write message of size "
+                << data_msg.data().contents().size();
+      if (WriteAll(ssh_stdin_in, data_msg.data().contents()) !=
+          data_msg.data().contents().size()) {
+        ssh_proc.Stop();
+        return CF_ERR("Failed to write contents: " << ssh_stdin_in->StrError());
+      }
+      LOG(INFO) << "successfully wrote message?";
+    }
+
+    ssh_stdin_in->Close();
+
+    auto ssh_ret = ssh_proc.Wait();
+    CF_EXPECT(ssh_ret == 0, "SSH command failed with code: " << ssh_ret);
+
+    return {};
+  }
+
+  GceApi& gce_;
+  BuildApi& build_;
+  google::protobuf::io::FileInputStream in_;
+  int out_;
+  bool internal_addresses_;
+
+  std::unordered_map<std::string, std::unique_ptr<ScopedGceInstance>>
+      instances_;
+};
+
+}  // namespace
+
+Result<void> TestGceDriverMain(int argc, char** argv) {
+  std::vector<Flag> flags;
+  std::string service_account_json_private_key_path = "";
+  flags.emplace_back(GflagsCompatFlag("service-account-json-private-key-path",
+                                      service_account_json_private_key_path));
+  std::string cloud_project = "";
+  flags.emplace_back(GflagsCompatFlag("cloud-project", cloud_project));
+  bool internal_addresses = false;
+  flags.emplace_back(
+      GflagsCompatFlag("internal-addresses", internal_addresses));
+
+  std::vector<std::string> args =
+      ArgsToVec(argc - 1, argv + 1);  // Skip argv[0]
+  CF_EXPECT(ParseFlags(flags, args), "Could not process command line flags.");
+
+  auto service_json =
+      CF_EXPECT(ReadJsonFromFile(service_account_json_private_key_path));
+
+  static constexpr char COMPUTE_SCOPE[] =
+      "https://www.googleapis.com/auth/compute";
+  auto curl = CurlWrapper::Create();
+  auto gce_creds = CF_EXPECT(ServiceAccountOauthCredentialSource::FromJson(
+      *curl, service_json, COMPUTE_SCOPE));
+
+  GceApi gce(*curl, gce_creds, cloud_project);
+
+  static constexpr char BUILD_SCOPE[] =
+      "https://www.googleapis.com/auth/androidbuild.internal";
+  auto build_creds = CF_EXPECT(ServiceAccountOauthCredentialSource::FromJson(
+      *curl, service_json, BUILD_SCOPE));
+
+  BuildApi build(*curl, &build_creds);
+
+  ReadEvalPrintLoop executor(gce, build, STDIN_FILENO, STDOUT_FILENO,
+                             internal_addresses);
+  LOG(INFO) << "Starting processing";
+  CF_EXPECT(executor.Process());
+
+  return {};
+}
+
+}  // namespace cuttlefish
+
+int main(int argc, char** argv) {
+  auto res = cuttlefish::TestGceDriverMain(argc, argv);
+  CHECK(res.ok()) << "cvd_test_gce_driver failed: " << res.error();
+}
diff --git a/host/commands/test_gce_driver/gce_api.cpp b/host/commands/test_gce_driver/gce_api.cpp
new file mode 100644
index 0000000..4922c59
--- /dev/null
+++ b/host/commands/test_gce_driver/gce_api.cpp
@@ -0,0 +1,536 @@
+//
+// 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.
+
+#include "host/commands/test_gce_driver/gce_api.h"
+
+#include <uuid.h>
+
+#include <sstream>
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "host/libs/web/credential_source.h"
+#include "host/libs/web/curl_wrapper.h"
+
+using android::base::Error;
+using android::base::Result;
+
+namespace cuttlefish {
+
+std::optional<std::string> OptStringMember(const Json::Value& jn,
+                                           const std::string& name) {
+  if (!jn.isMember(name) || jn[name].type() != Json::ValueType::stringValue) {
+    return {};
+  }
+  return jn[name].asString();
+}
+
+const Json::Value* OptObjMember(const Json::Value& jn,
+                                const std::string& name) {
+  if (!jn.isMember(name) || jn[name].type() != Json::ValueType::objectValue) {
+    return nullptr;
+  }
+  return &(jn[name]);
+}
+
+const Json::Value* OptArrayMember(const Json::Value& jn,
+                                  const std::string& name) {
+  if (!jn.isMember(name) || jn[name].type() != Json::ValueType::arrayValue) {
+    return nullptr;
+  }
+  return &(jn[name]);
+}
+
+Json::Value& EnsureObjMember(Json::Value& jn, const std::string& name) {
+  if (!jn.isMember(name) || jn[name].type() != Json::ValueType::objectValue) {
+    jn[name] = Json::Value(Json::ValueType::objectValue);
+  }
+  return jn[name];
+}
+
+Json::Value& EnsureArrayMember(Json::Value& jn, const std::string& name) {
+  if (!jn.isMember(name) || jn[name].type() != Json::ValueType::arrayValue) {
+    jn[name] = Json::Value(Json::ValueType::arrayValue);
+  }
+  return jn[name];
+}
+
+GceInstanceDisk::GceInstanceDisk(const Json::Value& json) : data_(json){};
+
+GceInstanceDisk GceInstanceDisk::EphemeralBootDisk() {
+  Json::Value initial_json(Json::ValueType::objectValue);
+  initial_json["type"] = "PERSISTENT";
+  initial_json["boot"] = true;
+  initial_json["mode"] = "READ_WRITE";
+  initial_json["autoDelete"] = true;
+  return GceInstanceDisk(initial_json);
+}
+
+constexpr char kGceDiskInitParams[] = "initializeParams";
+constexpr char kGceDiskName[] = "diskName";
+std::optional<std::string> GceInstanceDisk::Name() const {
+  const auto& init_params = OptObjMember(data_, kGceDiskInitParams);
+  if (!init_params) {
+    return {};
+  }
+  return OptStringMember(*init_params, kGceDiskName);
+}
+GceInstanceDisk& GceInstanceDisk::Name(const std::string& source) & {
+  EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskName] = source;
+  return *this;
+}
+GceInstanceDisk GceInstanceDisk::Name(const std::string& source) && {
+  EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskName] = source;
+  return *this;
+}
+
+constexpr char kGceDiskSourceImage[] = "sourceImage";
+std::optional<std::string> GceInstanceDisk::SourceImage() const {
+  const auto& init_params = OptObjMember(data_, kGceDiskInitParams);
+  if (!init_params) {
+    return {};
+  }
+  return OptStringMember(*init_params, kGceDiskSourceImage);
+}
+GceInstanceDisk& GceInstanceDisk::SourceImage(const std::string& source) & {
+  EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSourceImage] = source;
+  return *this;
+}
+GceInstanceDisk GceInstanceDisk::SourceImage(const std::string& source) && {
+  EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSourceImage] = source;
+  return *this;
+}
+
+constexpr char kGceDiskSizeGb[] = "diskSizeGb";
+GceInstanceDisk& GceInstanceDisk::SizeGb(uint64_t size) & {
+  EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSizeGb] = size;
+  return *this;
+}
+GceInstanceDisk GceInstanceDisk::SizeGb(uint64_t size) && {
+  EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSizeGb] = size;
+  return *this;
+}
+
+const Json::Value& GceInstanceDisk::AsJson() const { return data_; }
+
+GceNetworkInterface::GceNetworkInterface(const Json::Value& data)
+    : data_(data) {}
+
+constexpr char kGceNetworkAccessConfigs[] = "accessConfigs";
+GceNetworkInterface GceNetworkInterface::Default() {
+  Json::Value json{Json::ValueType::objectValue};
+  json["network"] = "global/networks/default";
+  Json::Value accessConfig{Json::ValueType::objectValue};
+  accessConfig["type"] = "ONE_TO_ONE_NAT";
+  accessConfig["name"] = "External NAT";
+  EnsureArrayMember(json, kGceNetworkAccessConfigs).append(accessConfig);
+  return GceNetworkInterface(json);
+}
+
+constexpr char kGceNetworkExternalIp[] = "natIP";
+std::optional<std::string> GceNetworkInterface::ExternalIp() const {
+  auto accessConfigs = OptArrayMember(data_, kGceNetworkAccessConfigs);
+  if (!accessConfigs || accessConfigs->size() < 1) {
+    return {};
+  }
+  if ((*accessConfigs)[0].type() != Json::ValueType::objectValue) {
+    return {};
+  }
+  return OptStringMember((*accessConfigs)[0], kGceNetworkExternalIp);
+}
+
+constexpr char kGceNetworkInternalIp[] = "networkIP";
+std::optional<std::string> GceNetworkInterface::InternalIp() const {
+  return OptStringMember(data_, kGceNetworkInternalIp);
+}
+
+const Json::Value& GceNetworkInterface::AsJson() const { return data_; }
+
+GceInstanceInfo::GceInstanceInfo(const Json::Value& json) : data_(json) {}
+
+constexpr char kGceZone[] = "zone";
+std::optional<std::string> GceInstanceInfo::Zone() const {
+  return OptStringMember(data_, kGceZone);
+}
+GceInstanceInfo& GceInstanceInfo::Zone(const std::string& zone) & {
+  data_[kGceZone] = zone;
+  return *this;
+}
+GceInstanceInfo GceInstanceInfo::Zone(const std::string& zone) && {
+  data_[kGceZone] = zone;
+  return *this;
+}
+
+constexpr char kGceName[] = "name";
+std::optional<std::string> GceInstanceInfo::Name() const {
+  return OptStringMember(data_, kGceName);
+}
+GceInstanceInfo& GceInstanceInfo::Name(const std::string& name) & {
+  data_[kGceName] = name;
+  return *this;
+}
+GceInstanceInfo GceInstanceInfo::Name(const std::string& name) && {
+  data_[kGceName] = name;
+  return *this;
+}
+
+constexpr char kGceMachineType[] = "machineType";
+std::optional<std::string> GceInstanceInfo::MachineType() const {
+  return OptStringMember(data_, kGceMachineType);
+}
+GceInstanceInfo& GceInstanceInfo::MachineType(const std::string& type) & {
+  data_[kGceMachineType] = type;
+  return *this;
+}
+GceInstanceInfo GceInstanceInfo::MachineType(const std::string& type) && {
+  data_[kGceMachineType] = type;
+  return *this;
+}
+
+constexpr char kGceDisks[] = "disks";
+GceInstanceInfo& GceInstanceInfo::AddDisk(const GceInstanceDisk& disk) & {
+  EnsureArrayMember(data_, kGceDisks).append(disk.AsJson());
+  return *this;
+}
+GceInstanceInfo GceInstanceInfo::AddDisk(const GceInstanceDisk& disk) && {
+  EnsureArrayMember(data_, kGceDisks).append(disk.AsJson());
+  return *this;
+}
+
+constexpr char kGceNetworkInterfaces[] = "networkInterfaces";
+GceInstanceInfo& GceInstanceInfo::AddNetworkInterface(
+    const GceNetworkInterface& net) & {
+  EnsureArrayMember(data_, kGceNetworkInterfaces).append(net.AsJson());
+  return *this;
+}
+GceInstanceInfo GceInstanceInfo::AddNetworkInterface(
+    const GceNetworkInterface& net) && {
+  EnsureArrayMember(data_, kGceNetworkInterfaces).append(net.AsJson());
+  return *this;
+}
+std::vector<GceNetworkInterface> GceInstanceInfo::NetworkInterfaces() const {
+  auto jsonNetworkInterfaces = OptArrayMember(data_, kGceNetworkInterfaces);
+  if (!jsonNetworkInterfaces) {
+    return {};
+  }
+  std::vector<GceNetworkInterface> interfaces;
+  for (const Json::Value& jsonNetworkInterface : *jsonNetworkInterfaces) {
+    interfaces.push_back(GceNetworkInterface(jsonNetworkInterface));
+  }
+  return interfaces;
+}
+
+constexpr char kGceMetadata[] = "metadata";
+constexpr char kGceMetadataItems[] = "items";
+constexpr char kGceMetadataKey[] = "key";
+constexpr char kGceMetadataValue[] = "value";
+GceInstanceInfo& GceInstanceInfo::AddMetadata(const std::string& key,
+                                              const std::string& value) & {
+  Json::Value item{Json::ValueType::objectValue};
+  item[kGceMetadataKey] = key;
+  item[kGceMetadataValue] = value;
+  auto& metadata = EnsureObjMember(data_, kGceMetadata);
+  EnsureArrayMember(metadata, kGceMetadataItems).append(item);
+  return *this;
+}
+GceInstanceInfo GceInstanceInfo::AddMetadata(const std::string& key,
+                                             const std::string& value) && {
+  Json::Value item{Json::ValueType::objectValue};
+  item[kGceMetadataKey] = key;
+  item[kGceMetadataValue] = value;
+  auto& metadata = EnsureObjMember(data_, kGceMetadata);
+  EnsureArrayMember(metadata, kGceMetadataItems).append(item);
+  return *this;
+}
+
+constexpr char kGceServiceAccounts[] = "serviceAccounts";
+constexpr char kGceScopes[] = "scopes";
+GceInstanceInfo& GceInstanceInfo::AddScope(const std::string& scope) & {
+  auto& serviceAccounts = EnsureArrayMember(data_, kGceServiceAccounts);
+  if (serviceAccounts.size() == 0) {
+    serviceAccounts.append(Json::Value(Json::ValueType::objectValue));
+  }
+  serviceAccounts[0]["email"] = "default";
+  auto& scopes = EnsureArrayMember(serviceAccounts[0], kGceScopes);
+  scopes.append(scope);
+  return *this;
+}
+GceInstanceInfo GceInstanceInfo::AddScope(const std::string& scope) && {
+  auto& serviceAccounts = EnsureArrayMember(data_, kGceServiceAccounts);
+  if (serviceAccounts.size() == 0) {
+    serviceAccounts.append(Json::Value(Json::ValueType::objectValue));
+  }
+  serviceAccounts[0]["email"] = "default";
+  auto& scopes = EnsureArrayMember(serviceAccounts[0], kGceScopes);
+  scopes.append(scope);
+  return *this;
+}
+
+const Json::Value& GceInstanceInfo::AsJson() const { return data_; }
+
+GceApi::GceApi(CurlWrapper& curl, CredentialSource& credentials,
+               const std::string& project)
+    : curl_(curl), credentials_(credentials), project_(project) {}
+
+std::vector<std::string> GceApi::Headers() {
+  return {
+      "Authorization:Bearer " + credentials_.Credential(),
+      "Content-Type: application/json",
+  };
+}
+
+class GceApi::Operation::Impl {
+ public:
+  Impl(GceApi& gce_api, std::function<Result<Json::Value>()> initial_request)
+      : gce_api_(gce_api), initial_request_(std::move(initial_request)) {
+    operation_future_ = std::async([this]() { return Run(); });
+  }
+
+  Result<bool> Run() {
+    auto initial_response = initial_request_();
+    if (!initial_response.ok()) {
+      return Error() << "Initial request failed: " << initial_response.error();
+    }
+
+    auto url = OptStringMember(*initial_response, "selfLink");
+    if (!url) {
+      return Error() << "Operation " << *initial_response
+                     << " was missing `selfLink` field.";
+    }
+    url = *url + "/wait";
+    running_ = true;
+
+    while (running_) {
+      auto response =
+          gce_api_.curl_.PostToJson(*url, std::string{""}, gce_api_.Headers());
+      const auto& json = response.data;
+      Json::Value errors;
+      if (auto j_error = OptObjMember(json, "error"); j_error) {
+        if (auto j_errors = OptArrayMember(*j_error, "errors"); j_errors) {
+          errors = j_errors->size() > 0 ? *j_errors : Json::Value();
+        }
+      }
+      Json::Value warnings;
+      if (auto j_warnings = OptArrayMember(json, "warnings"); j_warnings) {
+        warnings = j_warnings->size() > 0 ? *j_warnings : Json::Value();
+      }
+      LOG(DEBUG) << "Requested operation status at \"" << *url
+                 << "\", received " << json;
+      if (!response.HttpSuccess() || errors != Json::Value()) {
+        return Error() << "Error accessing \"" << *url
+                       << "\". Errors: " << errors
+                       << ", Warnings: " << warnings;
+      }
+      if (!json.isMember("status") ||
+          json["status"].type() != Json::ValueType::stringValue) {
+        return Error() << json << " \"status\" field invalid";
+      }
+      if (json["status"] == "DONE") {
+        return true;
+      }
+    }
+    return false;
+  }
+
+ private:
+  GceApi& gce_api_;
+  std::function<Result<Json::Value>()> initial_request_;
+  bool running_;
+  std::future<Result<bool>> operation_future_;
+  friend class GceApi::Operation;
+};
+
+GceApi::Operation::Operation(std::unique_ptr<GceApi::Operation::Impl> impl)
+    : impl_(std::move(impl)) {}
+
+GceApi::Operation::~Operation() = default;
+
+void GceApi::Operation::StopWaiting() { impl_->running_ = false; }
+
+std::future<Result<bool>>& GceApi::Operation::Future() {
+  return impl_->operation_future_;
+}
+
+static std::string RandomUuid() {
+  uuid_t uuid;
+  uuid_generate_random(uuid);
+  std::string uuid_str = "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx";
+  uuid_unparse(uuid, uuid_str.data());
+  return uuid_str;
+}
+
+// GCE gives back full URLs for zones, but it only wants the last part in
+// requests
+static std::string SanitizeZone(const std::string& zone) {
+  auto last_slash = zone.rfind("/");
+  if (last_slash == std::string::npos) {
+    return zone;
+  }
+  return zone.substr(last_slash + 1);
+}
+
+std::future<Result<GceInstanceInfo>> GceApi::Get(
+    const GceInstanceInfo& instance) {
+  auto name = instance.Name();
+  if (!name) {
+    auto task = [json = instance.AsJson()]() -> Result<GceInstanceInfo> {
+      return Error() << "Missing a name for \"" << json << "\"";
+    };
+    return std::async(std::launch::deferred, task);
+  }
+  auto zone = instance.Zone();
+  if (!zone) {
+    auto task = [json = instance.AsJson()]() -> Result<GceInstanceInfo> {
+      return Error() << "Missing a zone for \"" << json << "\"";
+    };
+    return std::async(std::launch::deferred, task);
+  }
+  return Get(*zone, *name);
+}
+
+std::future<Result<GceInstanceInfo>> GceApi::Get(const std::string& zone,
+                                                 const std::string& name) {
+  std::stringstream url;
+  url << "https://compute.googleapis.com/compute/v1";
+  url << "/projects/" << curl_.UrlEscape(project_);
+  url << "/zones/" << curl_.UrlEscape(SanitizeZone(zone));
+  url << "/instances/" << curl_.UrlEscape(name);
+  auto task = [this, url = url.str()]() -> Result<GceInstanceInfo> {
+    auto response = curl_.DownloadToJson(url, Headers());
+    if (!response.HttpSuccess()) {
+      return Error() << "Failed to get instance info, received "
+                     << response.data << " with code " << response.http_code;
+    }
+    return GceInstanceInfo(response.data);
+  };
+  return std::async(task);
+}
+
+GceApi::Operation GceApi::Insert(const Json::Value& request) {
+  if (!request.isMember("zone") ||
+      request["zone"].type() != Json::ValueType::stringValue) {
+    auto task = [request]() -> Result<Json::Value> {
+      return Error() << "Missing a zone for \"" << request << "\"";
+    };
+    return Operation(
+        std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+  }
+  auto zone = request["zone"].asString();
+  Json::Value requestNoZone = request;
+  requestNoZone.removeMember("zone");
+  std::stringstream url;
+  url << "https://compute.googleapis.com/compute/v1";
+  url << "/projects/" << curl_.UrlEscape(project_);
+  url << "/zones/" << curl_.UrlEscape(SanitizeZone(zone));
+  url << "/instances";
+  url << "?requestId=" << RandomUuid();  // Avoid duplication on request retry
+  auto task = [this, requestNoZone, url = url.str()]() -> Result<Json::Value> {
+    auto response = curl_.PostToJson(url, requestNoZone, Headers());
+    if (!response.HttpSuccess()) {
+      return Error() << "Failed to create instance: " << response.data
+                     << ". Sent request " << requestNoZone;
+    }
+    return response.data;
+  };
+  return Operation(
+      std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+}
+
+GceApi::Operation GceApi::Insert(const GceInstanceInfo& request) {
+  return Insert(request.AsJson());
+}
+
+GceApi::Operation GceApi::Reset(const std::string& zone,
+                                const std::string& name) {
+  std::stringstream url;
+  url << "https://compute.googleapis.com/compute/v1";
+  url << "/projects/" << curl_.UrlEscape(project_);
+  url << "/zones/" << curl_.UrlEscape(SanitizeZone(zone));
+  url << "/instances/" << curl_.UrlEscape(name);
+  url << "/reset";
+  url << "?requestId=" << RandomUuid();  // Avoid duplication on request retry
+  auto task = [this, url = url.str()]() -> Result<Json::Value> {
+    auto response = curl_.PostToJson(url, Json::Value(), Headers());
+    if (!response.HttpSuccess()) {
+      return Error() << "Failed to create instance: " << response.data;
+    }
+    return response.data;
+  };
+  return Operation(
+      std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+}
+
+GceApi::Operation GceApi::Reset(const GceInstanceInfo& instance) {
+  auto name = instance.Name();
+  if (!name) {
+    auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
+      return Error() << "Missing a name for \"" << json << "\"";
+    };
+    return Operation(
+        std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+  }
+  auto zone = instance.Zone();
+  if (!zone) {
+    auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
+      return Error() << "Missing a zone for \"" << json << "\"";
+    };
+    return Operation(
+        std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+  }
+  return Reset(*zone, *name);
+}
+
+GceApi::Operation GceApi::Delete(const std::string& zone,
+                                 const std::string& name) {
+  std::stringstream url;
+  url << "https://compute.googleapis.com/compute/v1";
+  url << "/projects/" << curl_.UrlEscape(project_);
+  url << "/zones/" << curl_.UrlEscape(SanitizeZone(zone));
+  url << "/instances/" << curl_.UrlEscape(name);
+  url << "?requestId=" << RandomUuid();  // Avoid duplication on request retry
+  auto task = [this, url = url.str()]() -> Result<Json::Value> {
+    auto response = curl_.DeleteToJson(url, Headers());
+    if (!response.HttpSuccess()) {
+      return Error() << "Failed to delete instance: " << response.data;
+    }
+    return response.data;
+  };
+  return Operation(
+      std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+}
+
+GceApi::Operation GceApi::Delete(const GceInstanceInfo& instance) {
+  auto name = instance.Name();
+  if (!name) {
+    auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
+      return Error() << "Missing a name for \"" << json << "\"";
+    };
+    return Operation(
+        std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+  }
+  auto zone = instance.Zone();
+  if (!zone) {
+    auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
+      return Error() << "Missing a zone for \"" << json << "\"";
+    };
+    return Operation(
+        std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
+  }
+  return Delete(*zone, *name);
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/test_gce_driver/gce_api.h b/host/commands/test_gce_driver/gce_api.h
new file mode 100644
index 0000000..e4d605f
--- /dev/null
+++ b/host/commands/test_gce_driver/gce_api.h
@@ -0,0 +1,147 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <future>
+#include <optional>
+#include <string>
+
+#include <android-base/result.h>
+#include <json/json.h>
+
+#include "host/libs/web/credential_source.h"
+#include "host/libs/web/curl_wrapper.h"
+
+namespace cuttlefish {
+
+class GceInstanceDisk {
+ public:
+  GceInstanceDisk() = default;
+  explicit GceInstanceDisk(const Json::Value&);
+
+  static GceInstanceDisk EphemeralBootDisk();
+
+  std::optional<std::string> Name() const;
+  GceInstanceDisk& Name(const std::string&) &;
+  GceInstanceDisk Name(const std::string&) &&;
+
+  std::optional<std::string> SourceImage() const;
+  GceInstanceDisk& SourceImage(const std::string&) &;
+  GceInstanceDisk SourceImage(const std::string&) &&;
+
+  GceInstanceDisk& SizeGb(uint64_t gb) &;
+  GceInstanceDisk SizeGb(uint64_t gb) &&;
+
+  const Json::Value& AsJson() const;
+
+ private:
+  Json::Value data_;
+};
+
+class GceNetworkInterface {
+ public:
+  GceNetworkInterface() = default;
+  explicit GceNetworkInterface(const Json::Value&);
+
+  static GceNetworkInterface Default();
+
+  std::optional<std::string> ExternalIp() const;
+  std::optional<std::string> InternalIp() const;
+
+  const Json::Value& AsJson() const;
+
+ private:
+  Json::Value data_;
+};
+
+class GceInstanceInfo {
+ public:
+  GceInstanceInfo() = default;
+  explicit GceInstanceInfo(const Json::Value&);
+
+  std::optional<std::string> Zone() const;
+  GceInstanceInfo& Zone(const std::string&) &;
+  GceInstanceInfo Zone(const std::string&) &&;
+
+  std::optional<std::string> Name() const;
+  GceInstanceInfo& Name(const std::string&) &;
+  GceInstanceInfo Name(const std::string&) &&;
+
+  std::optional<std::string> MachineType() const;
+  GceInstanceInfo& MachineType(const std::string&) &;
+  GceInstanceInfo MachineType(const std::string&) &&;
+
+  GceInstanceInfo& AddDisk(const GceInstanceDisk&) &;
+  GceInstanceInfo AddDisk(const GceInstanceDisk&) &&;
+
+  GceInstanceInfo& AddNetworkInterface(const GceNetworkInterface&) &;
+  GceInstanceInfo AddNetworkInterface(const GceNetworkInterface&) &&;
+  std::vector<GceNetworkInterface> NetworkInterfaces() const;
+
+  GceInstanceInfo& AddMetadata(const std::string&, const std::string&) &;
+  GceInstanceInfo AddMetadata(const std::string&, const std::string&) &&;
+
+  GceInstanceInfo& AddScope(const std::string&) &;
+  GceInstanceInfo AddScope(const std::string&) &&;
+
+  const Json::Value& AsJson() const;
+
+ private:
+  Json::Value data_;
+};
+
+class GceApi {
+ public:
+  class Operation {
+   public:
+    ~Operation();
+    void StopWaiting();
+    /// `true` means it waited to completion, `false` means it was cancelled
+    std::future<android::base::Result<bool>>& Future();
+
+   private:
+    class Impl;
+    std::unique_ptr<Impl> impl_;
+    Operation(std::unique_ptr<Impl>);
+    friend class GceApi;
+  };
+
+  GceApi(CurlWrapper&, CredentialSource& credentials,
+         const std::string& project);
+
+  std::future<android::base::Result<GceInstanceInfo>> Get(
+      const GceInstanceInfo&);
+  std::future<android::base::Result<GceInstanceInfo>> Get(
+      const std::string& zone, const std::string& name);
+
+  Operation Insert(const Json::Value&);
+  Operation Insert(const GceInstanceInfo&);
+
+  Operation Reset(const std::string& zone, const std::string& name);
+  Operation Reset(const GceInstanceInfo&);
+
+  Operation Delete(const std::string& zone, const std::string& name);
+  Operation Delete(const GceInstanceInfo&);
+
+ private:
+  std::vector<std::string> Headers();
+
+  CurlWrapper& curl_;
+  CredentialSource& credentials_;
+  std::string project_;
+};
+
+}  // namespace cuttlefish
diff --git a/host/commands/test_gce_driver/key_pair.cpp b/host/commands/test_gce_driver/key_pair.cpp
new file mode 100644
index 0000000..5435f50
--- /dev/null
+++ b/host/commands/test_gce_driver/key_pair.cpp
@@ -0,0 +1,160 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/commands/test_gce_driver/key_pair.h"
+
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+
+#include <memory>
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/result.h>
+
+#include "common/libs/utils/subprocess.h"
+
+using android::base::Error;
+using android::base::Result;
+
+namespace cuttlefish {
+
+static int SslRecordErrCallback(const char* str, size_t len, void* data) {
+  *reinterpret_cast<std::string*>(data) = std::string(str, len);
+  return 1;  // success
+}
+
+class BoringSslKeyPair : public KeyPair {
+ public:
+  /*
+   * We interact with boringssl directly here to avoid ssh-keygen writing
+   * directly to the filesystem. The relevant ssh-keygen command here is
+   *
+   * $ ssh-keygen -t rsa -N "" -f ${TARGET}
+   *
+   * which unfortunately tries to write to `${TARGET}.pub`, making it hard to
+   * use something like /dev/stdout or /proc/self/fd/1 to get the keys.
+   */
+  static Result<std::unique_ptr<KeyPair>> CreateRsa(size_t bytes) {
+    std::unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX*)> ctx{
+        EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL), EVP_PKEY_CTX_free};
+    std::string error;
+    if (!ctx) {
+      ERR_print_errors_cb(SslRecordErrCallback, &error);
+      return Error() << "EVP_PKEY_CTX_new_id failed: " << error;
+    }
+    if (EVP_PKEY_keygen_init(ctx.get()) <= 0) {
+      ERR_print_errors_cb(SslRecordErrCallback, &error);
+      return Error() << "EVP_PKEY_keygen_init failed: " << error;
+    }
+    if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), bytes) <= 0) {
+      ERR_print_errors_cb(SslRecordErrCallback, &error);
+      return Error() << "EVP_PKEY_CTX_set_rsa_keygen_bits failed: " << error;
+    }
+
+    EVP_PKEY* pkey = nullptr;
+    if (EVP_PKEY_keygen(ctx.get(), &pkey) <= 0) {
+      ERR_print_errors_cb(SslRecordErrCallback, &error);
+      return Error() << "EVP_PKEY_keygen failed: " << error;
+    }
+    return std::unique_ptr<KeyPair>{new BoringSslKeyPair(pkey)};
+  }
+
+  Result<std::string> PemPrivateKey() const override {
+    std::unique_ptr<BIO, int (*)(BIO*)> bo(BIO_new(BIO_s_mem()), BIO_free);
+    std::string error;
+    if (!bo) {
+      ERR_print_errors_cb(SslRecordErrCallback, &error);
+      return Error() << "BIO_new failed: " << error;
+    }
+    if (!PEM_write_bio_PrivateKey(bo.get(), pkey_.get(), NULL, NULL, 0, 0,
+                                  NULL)) {
+      ERR_print_errors_cb(SslRecordErrCallback, &error);
+      return Error() << "PEM_write_bio_PrivateKey failed: " << error;
+    }
+    std::string priv(BIO_pending(bo.get()), ' ');
+    auto written = BIO_read(bo.get(), priv.data(), priv.size());
+    if (written != priv.size()) {
+      return Error() << "Unexpected amount of data written: " << written
+                     << " != " << priv.size();
+    }
+    return priv;
+  }
+
+  Result<std::string> PemPublicKey() const override {
+    std::unique_ptr<BIO, int (*)(BIO*)> bo(BIO_new(BIO_s_mem()), BIO_free);
+    std::string error;
+    if (!bo) {
+      ERR_print_errors_cb(SslRecordErrCallback, &error);
+      return Error() << "BIO_new failed: " << error;
+    }
+    if (!PEM_write_bio_PUBKEY(bo.get(), pkey_.get())) {
+      ERR_print_errors_cb(SslRecordErrCallback, &error);
+      return Error() << "PEM_write_bio_PUBKEY failed: " << error;
+    }
+
+    std::string priv(BIO_pending(bo.get()), ' ');
+    auto written = BIO_read(bo.get(), priv.data(), priv.size());
+    if (written != priv.size()) {
+      return Error() << "Unexpected amount of data written: " << written
+                     << " != " << priv.size();
+    }
+    return priv;
+  }
+
+  /*
+   * OpenSSH has its own distinct format for public keys, which cannot be
+   * produced directly with OpenSSL/BoringSSL primitives. Luckily it is possible
+   * to convert the BoringSSL-generated RSA key without touching the filesystem.
+   */
+  Result<std::string> OpenSshPublicKey() const override {
+    auto pem_pubkey = PemPublicKey();
+    if (!pem_pubkey.ok()) {
+      return Error() << "Failed to get pem public key: " << pem_pubkey.error();
+    }
+    auto fd = SharedFD::MemfdCreateWithData("", *pem_pubkey);
+    if (!fd->IsOpen()) {
+      return Error() << "Could not create pubkey memfd: " << fd->StrError();
+    }
+    Command cmd("/usr/bin/ssh-keygen");
+    cmd.AddParameter("-i");
+    cmd.AddParameter("-f");
+    cmd.AddParameter("/proc/self/fd/0");
+    cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, fd);
+    cmd.AddParameter("-m");
+    cmd.AddParameter("PKCS8");
+    std::string out;
+    std::string err;
+    auto ret = RunWithManagedStdio(std::move(cmd), nullptr, &out, &err);
+    if (ret != 0) {
+      return Error() << "Could not convert pem key to openssh key. "
+                     << "stdout=\"" << out << "\", stderr=\"" << err << "\"";
+    }
+    return out;
+  }
+
+ private:
+  BoringSslKeyPair(EVP_PKEY* pkey) : pkey_(pkey, EVP_PKEY_free) {}
+
+  std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)> pkey_;
+};
+
+Result<std::unique_ptr<KeyPair>> KeyPair::CreateRsa(size_t bytes) {
+  return BoringSslKeyPair::CreateRsa(bytes);
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/test_gce_driver/key_pair.h b/host/commands/test_gce_driver/key_pair.h
new file mode 100644
index 0000000..570a7e6
--- /dev/null
+++ b/host/commands/test_gce_driver/key_pair.h
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include <android-base/result.h>
+
+namespace cuttlefish {
+
+struct KeyPair {
+ public:
+  static android::base::Result<std::unique_ptr<KeyPair>> CreateRsa(
+      size_t bytes);
+  virtual ~KeyPair() = default;
+
+  virtual android::base::Result<std::string> PemPrivateKey() const = 0;
+  virtual android::base::Result<std::string> PemPublicKey() const = 0;
+  virtual android::base::Result<std::string> OpenSshPublicKey() const = 0;
+};
+
+};  // namespace cuttlefish
diff --git a/host/commands/test_gce_driver/scoped_instance.cpp b/host/commands/test_gce_driver/scoped_instance.cpp
new file mode 100644
index 0000000..3c13d91
--- /dev/null
+++ b/host/commands/test_gce_driver/scoped_instance.cpp
@@ -0,0 +1,225 @@
+//
+// 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.
+
+#include "host/commands/test_gce_driver/scoped_instance.h"
+
+#include <netinet/ip.h>
+
+#include <random>
+#include <sstream>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/result.h>
+
+#include "common/libs/fs/shared_buf.h"
+
+using android::base::Error;
+using android::base::Result;
+
+namespace cuttlefish {
+
+SshCommand& SshCommand::PrivKey(const std::string& privkey_path) & {
+  privkey_path_ = privkey_path;
+  return *this;
+}
+SshCommand SshCommand::PrivKey(const std::string& privkey_path) && {
+  privkey_path_ = privkey_path;
+  return *this;
+}
+
+SshCommand& SshCommand::WithoutKnownHosts() & {
+  without_known_hosts_ = true;
+  return *this;
+}
+SshCommand SshCommand::WithoutKnownHosts() && {
+  without_known_hosts_ = true;
+  return *this;
+}
+
+SshCommand& SshCommand::Username(const std::string& username) & {
+  username_ = username;
+  return *this;
+}
+SshCommand SshCommand::Username(const std::string& username) && {
+  username_ = username;
+  return *this;
+}
+
+SshCommand& SshCommand::Host(const std::string& host) & {
+  host_ = host;
+  return *this;
+}
+SshCommand SshCommand::Host(const std::string& host) && {
+  host_ = host;
+  return *this;
+}
+
+SshCommand& SshCommand::RemotePortForward(uint16_t remote, uint16_t local) & {
+  remote_port_forwards_.push_back({remote, local});
+  return *this;
+}
+SshCommand SshCommand::RemotePortForward(uint16_t remote, uint16_t local) && {
+  remote_port_forwards_.push_back({remote, local});
+  return *this;
+}
+
+SshCommand& SshCommand::RemoteParameter(const std::string& param) & {
+  parameters_.push_back(param);
+  return *this;
+}
+SshCommand SshCommand::RemoteParameter(const std::string& param) && {
+  parameters_.push_back(param);
+  return *this;
+}
+
+Command SshCommand::Build() const {
+  Command remote_cmd{"/usr/bin/ssh"};
+  if (privkey_path_) {
+    remote_cmd.AddParameter("-i");
+    remote_cmd.AddParameter(*privkey_path_);
+  }
+  if (without_known_hosts_) {
+    remote_cmd.AddParameter("-o");
+    remote_cmd.AddParameter("StrictHostKeyChecking=no");
+    remote_cmd.AddParameter("-o");
+    remote_cmd.AddParameter("UserKnownHostsFile=/dev/null");
+  }
+  for (const auto& fwd : remote_port_forwards_) {
+    remote_cmd.AddParameter("-R");
+    remote_cmd.AddParameter(fwd.remote_port, ":127.0.0.1:", fwd.local_port);
+  }
+  if (host_) {
+    remote_cmd.AddParameter(username_ ? *username_ + "@" : "", *host_);
+  }
+  for (const auto& param : parameters_) {
+    remote_cmd.AddParameter(param);
+  }
+  return remote_cmd;
+}
+
+Result<std::unique_ptr<ScopedGceInstance>> ScopedGceInstance::CreateDefault(
+    GceApi& gce, const std::string& zone, const std::string& instance_name,
+    bool internal) {
+  auto ssh_key = KeyPair::CreateRsa(4096);
+  if (!ssh_key.ok()) {
+    return Error() << "Could not create ssh key pair: " << ssh_key.error();
+  }
+
+  auto ssh_pubkey = (*ssh_key)->OpenSshPublicKey();
+  if (!ssh_pubkey.ok()) {
+    return Error() << "Could get openssh format key: " << ssh_pubkey.error();
+  }
+
+  auto default_instance_info =
+      GceInstanceInfo()
+          .Name(instance_name)
+          .Zone(zone)
+          .MachineType("zones/us-west1-a/machineTypes/n1-standard-4")
+          .AddMetadata("ssh-keys", "vsoc-01:" + *ssh_pubkey)
+          .AddNetworkInterface(GceNetworkInterface::Default())
+          .AddDisk(
+              GceInstanceDisk::EphemeralBootDisk()
+                  .SourceImage(
+                      "projects/cloud-android-releases/global/images/family/"
+                      "cuttlefish-google")
+                  .SizeGb(30))
+          .AddScope("https://www.googleapis.com/auth/androidbuild.internal")
+          .AddScope("https://www.googleapis.com/auth/devstorage.read_only")
+          .AddScope("https://www.googleapis.com/auth/logging.write");
+
+  auto creation = gce.Insert(default_instance_info).Future().get();
+  if (!creation.ok()) {
+    return Error() << "Failed to create instance: " << creation.error();
+  }
+
+  auto privkey = CF_EXPECT((*ssh_key)->PemPrivateKey());
+  std::unique_ptr<TemporaryFile> privkey_file(CF_EXPECT(new TemporaryFile()));
+  auto fd_dup = SharedFD::Dup(privkey_file->fd);
+  CF_EXPECT(fd_dup->IsOpen());
+  CF_EXPECT(WriteAll(fd_dup, privkey) == privkey.size());
+  fd_dup->Close();
+
+  std::unique_ptr<ScopedGceInstance> instance(new ScopedGceInstance(
+      gce, default_instance_info, std::move(privkey_file), internal));
+
+  auto created_info = gce.Get(default_instance_info).get();
+  if (!created_info.ok()) {
+    return Error() << "Failed to get instance info: " << created_info.error();
+  }
+  instance->instance_ = *created_info;
+
+  auto ssh_ready = instance->EnforceSshReady();
+  if (!ssh_ready.ok()) {
+    return Error() << "Failed to access SSH on instance: " << ssh_ready.error();
+  }
+  return instance;
+}
+
+Result<void> ScopedGceInstance::EnforceSshReady() {
+  std::string out;
+  std::string err;
+  for (int i = 0; i < 100; i++) {
+    auto ssh = Ssh();
+    if (!ssh.ok()) {
+      return Error() << "Failed to create ssh command: " << ssh.error();
+    }
+
+    ssh->RemoteParameter("ls");
+    ssh->RemoteParameter("/");
+    auto command = ssh->Build();
+
+    out = "";
+    err = "";
+    int ret = RunWithManagedStdio(std::move(command), nullptr, &out, &err);
+    if (ret == 0) {
+      return {};
+    }
+  }
+
+  return Error() << "Failed to ssh to the instance. stdout=\"" << out
+                 << "\", stderr = \"" << err << "\"";
+}
+
+ScopedGceInstance::ScopedGceInstance(GceApi& gce,
+                                     const GceInstanceInfo& instance,
+                                     std::unique_ptr<TemporaryFile> privkey,
+                                     bool use_internal_address)
+    : gce_(gce),
+      instance_(instance),
+      privkey_(std::move(privkey)),
+      use_internal_address_(use_internal_address) {}
+
+ScopedGceInstance::~ScopedGceInstance() {
+  auto delete_ins = gce_.Delete(instance_).Future().get();
+  if (!delete_ins.ok()) {
+    LOG(ERROR) << "Failed to delete instance: " << delete_ins.error();
+  }
+}
+
+Result<SshCommand> ScopedGceInstance::Ssh() {
+  const auto& network_interfaces = instance_.NetworkInterfaces();
+  CF_EXPECT(!network_interfaces.empty());
+  auto iface = network_interfaces[0];
+  auto ip = use_internal_address_ ? iface.InternalIp() : iface.ExternalIp();
+  CF_EXPECT(ip.has_value());
+  return SshCommand()
+      .PrivKey(privkey_->path)
+      .WithoutKnownHosts()
+      .Username("vsoc-01")
+      .Host(*ip);
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/test_gce_driver/scoped_instance.h b/host/commands/test_gce_driver/scoped_instance.h
new file mode 100644
index 0000000..f4ee87f
--- /dev/null
+++ b/host/commands/test_gce_driver/scoped_instance.h
@@ -0,0 +1,94 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/result.h>
+
+#include "common/libs/utils/subprocess.h"
+#include "host/commands/test_gce_driver/gce_api.h"
+#include "host/commands/test_gce_driver/key_pair.h"
+
+namespace cuttlefish {
+
+// TODO(schuffelen): Implement this with libssh2
+class SshCommand {
+ public:
+  SshCommand() = default;
+
+  SshCommand& PrivKey(const std::string& path) &;
+  SshCommand PrivKey(const std::string& path) &&;
+
+  SshCommand& WithoutKnownHosts() &;
+  SshCommand WithoutKnownHosts() &&;
+
+  SshCommand& Username(const std::string& username) &;
+  SshCommand Username(const std::string& username) &&;
+
+  SshCommand& Host(const std::string& host) &;
+  SshCommand Host(const std::string& host) &&;
+
+  SshCommand& RemotePortForward(uint16_t remote, uint16_t local) &;
+  SshCommand RemotePortForward(uint16_t remote, uint16_t local) &&;
+
+  SshCommand& RemoteParameter(const std::string& param) &;
+  SshCommand RemoteParameter(const std::string& param) &&;
+
+  Command Build() const;
+
+ private:
+  struct RemotePortForwardType {
+    uint16_t remote_port;
+    uint16_t local_port;
+  };
+
+  std::optional<std::string> privkey_path_;
+  bool without_known_hosts_;
+  std::optional<std::string> username_;
+  std::optional<std::string> host_;
+  std::vector<RemotePortForwardType> remote_port_forwards_;
+  std::vector<std::string> parameters_;
+};
+
+class ScopedGceInstance {
+ public:
+  static android::base::Result<std::unique_ptr<ScopedGceInstance>>
+  CreateDefault(GceApi& gce, const std::string& zone,
+                const std::string& instance_name, bool internal_addresses);
+  ~ScopedGceInstance();
+
+  android::base::Result<SshCommand> Ssh();
+  android::base::Result<void> Reset();
+
+ private:
+  ScopedGceInstance(GceApi& gce, const GceInstanceInfo& instance,
+                    std::unique_ptr<TemporaryFile> privkey,
+                    bool internal_addresses);
+
+  android::base::Result<void> EnforceSshReady();
+
+  GceApi& gce_;
+  GceInstanceInfo instance_;
+  std::unique_ptr<TemporaryFile> privkey_;
+  bool use_internal_address_;
+};
+
+}  // namespace cuttlefish
diff --git a/host/commands/test_gce_driver/test_gce_driver.proto b/host/commands/test_gce_driver/test_gce_driver.proto
new file mode 100644
index 0000000..128d0fd
--- /dev/null
+++ b/host/commands/test_gce_driver/test_gce_driver.proto
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package cuttlefish.test_gce_driver;
+
+option java_multiple_files = true;
+option java_package = "com.android.cuttlefish.test";
+option java_outer_classname = "TestGceDriverProtos";
+
+message TestMessage {
+  oneof contents {
+    Exit exit = 1;
+    StreamEnd stream_end = 2;
+    Error error = 3;
+    CreateInstance create_instance = 4;
+    SshCommand ssh_command = 5;
+    Data data = 6;
+    UploadBuildArtifact upload_build_artifact = 7;
+    UploadFile upload_file = 8;
+  }
+}
+
+message Exit {}
+
+message StreamEnd {}
+
+message Error {
+  string text = 1;
+}
+
+message GceInstanceId {
+  string name = 1;
+  string zone = 2;
+}
+
+message CreateInstance {
+  GceInstanceId id = 1;
+}
+
+message SshCommand {
+  GceInstanceId instance = 1;
+  repeated string arguments = 2;
+}
+
+enum DataType {
+  DATA_TYPE_UNSPECIFIED = 0;
+  DATA_TYPE_STDOUT = 1;
+  DATA_TYPE_STDERR = 2;
+  DATA_TYPE_RETURN_CODE = 3;
+  DATA_TYPE_FILE_CONTENTS = 4;
+}
+
+message Data {
+  DataType type = 1;
+  bytes contents = 2;
+}
+
+message Build {
+  string id = 1;
+  string target = 2;
+}
+
+message UploadBuildArtifact {
+  GceInstanceId instance = 1;
+  Build build = 2;
+  string artifact_name = 3;
+  string remote_path = 4;
+}
+
+message UploadFile {
+  GceInstanceId instance = 1;
+  string remote_path = 2;
+}
diff --git a/host/commands/tombstone_receiver/Android.bp b/host/commands/tombstone_receiver/Android.bp
index 51830fc..b50cde4 100644
--- a/host/commands/tombstone_receiver/Android.bp
+++ b/host/commands/tombstone_receiver/Android.bp
@@ -23,6 +23,7 @@
         "main.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libjsoncpp",
diff --git a/host/commands/tombstone_receiver/main.cpp b/host/commands/tombstone_receiver/main.cpp
index 2e0ea69..5fa495d 100644
--- a/host/commands/tombstone_receiver/main.cpp
+++ b/host/commands/tombstone_receiver/main.cpp
@@ -22,23 +22,21 @@
 #include <iomanip>
 #include <sstream>
 
-#include "host/libs/config/logging.h"
 #include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/shared_fd_flag.h"
+#include "host/libs/config/logging.h"
 
-DEFINE_int32(
-    server_fd, -1,
-    "File descriptor to an already created vsock server. If negative a new "
-    "server will be created at the port specified on the config file");
-DEFINE_string(tombstone_dir, "", "directory to write out tombstones in");
+namespace cuttlefish {
 
 static uint num_tombstones_in_last_second = 0;
 static std::string last_tombstone_name = "";
 
-static std::string next_tombstone_path() {
+static std::string next_tombstone_path(const std::string& tombstone_dir) {
   auto in_time_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
   std::stringstream ss;
-  ss << FLAGS_tombstone_dir << "/tombstone_" <<
-    std::put_time(std::gmtime(&in_time_t), "%Y-%m-%d-%H%M%S");
+  ss << tombstone_dir << "/tombstone_"
+     << std::put_time(std::gmtime(&in_time_t), "%Y-%m-%d-%H%M%S");
   auto retval = ss.str();
 
   // Gives tombstones unique names
@@ -54,23 +52,38 @@
   return retval;
 }
 
-#define CHUNK_RECV_MAX_LEN (1024)
-int main(int argc, char** argv) {
-  cuttlefish::DefaultSubprocessLogging(argv);
-  google::ParseCommandLineFlags(&argc, &argv, true);
+static constexpr size_t CHUNK_RECV_MAX_LEN = 1024;
 
-  cuttlefish::SharedFD server_fd = cuttlefish::SharedFD::Dup(FLAGS_server_fd);
-  close(FLAGS_server_fd);
+int TombstoneReceiverMain(int argc, char** argv) {
+  DefaultSubprocessLogging(argv);
 
-  CHECK(server_fd->IsOpen()) << "Error inheriting tombstone server: "
-                             << server_fd->StrError();
+  std::vector<Flag> flags;
+
+  std::string tombstone_dir;
+  flags.emplace_back(GflagsCompatFlag("tombstone_dir", tombstone_dir)
+                         .Help("directory to write out tombstones in"));
+
+  SharedFD server_fd;
+  flags.emplace_back(
+      SharedFDFlag("server_fd", server_fd)
+          .Help("File descriptor to an already created vsock server"));
+
+  flags.emplace_back(HelpFlag(flags));
+  flags.emplace_back(UnexpectedArgumentGuard());
+
+  std::vector<std::string> args =
+      ArgsToVec(argc - 1, argv + 1);  // Skip argv[0]
+  CHECK(ParseFlags(flags, args)) << "Could not process command line flags.";
+
+  CHECK(server_fd->IsOpen()) << "Did not receive a server fd";
+
   LOG(DEBUG) << "Host is starting server on port "
              << server_fd->VsockServerPort();
 
   // Server loop
   while (true) {
-    auto conn = cuttlefish::SharedFD::Accept(*server_fd);
-    std::ofstream file(next_tombstone_path(),
+    auto conn = SharedFD::Accept(*server_fd);
+    std::ofstream file(next_tombstone_path(tombstone_dir),
                        std::ofstream::out | std::ofstream::binary);
 
     while (file.is_open()) {
@@ -87,3 +100,9 @@
 
   return 0;
 }
+
+}  // namespace cuttlefish
+
+int main(int argc, char** argv) {
+  return cuttlefish::TombstoneReceiverMain(argc, argv);
+}
diff --git a/host/commands/mk_cdisk/Android.bp b/host/commands/wmediumd_control/Android.bp
similarity index 78%
rename from host/commands/mk_cdisk/Android.bp
rename to host/commands/wmediumd_control/Android.bp
index a0cf8ba..0e58ec3 100644
--- a/host/commands/mk_cdisk/Android.bp
+++ b/host/commands/wmediumd_control/Android.bp
@@ -18,24 +18,26 @@
 }
 
 cc_binary {
-    name: "mk_cdisk",
+    name: "wmediumd_control",
     srcs: [
-        "mk_cdisk.cc",
+        "main.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libbase",
+        "libfruit",
         "libjsoncpp",
-        "liblog",
         "libz",
     ],
     static_libs: [
-        "libcdisk_spec",
-        "libext2_uuid",
-        "libimage_aggregator",
-        "libprotobuf-cpp-lite",
-        "libsparse",
+        "libcuttlefish_host_config",
+        "libcuttlefish_wmediumd_controller",
+        "libgflags",
+    ],
+    header_libs: [
+        "wmediumd_headers",
     ],
     defaults: ["cuttlefish_host"],
 }
diff --git a/host/commands/wmediumd_control/main.cpp b/host/commands/wmediumd_control/main.cpp
new file mode 100644
index 0000000..63763b5
--- /dev/null
+++ b/host/commands/wmediumd_control/main.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <gflags/gflags.h>
+
+#include <cstdlib>
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/wmediumd_controller/wmediumd_controller.h"
+
+const std::string usageMessage =
+    "wmediumd control commandline utility\n\n"
+    "  Usage: wmediumd_control [option] command [args...]\n\n"
+    "  Commands:\n\n"
+    "    set_snr mac1 mac2 snr\n"
+    "      set SNR between two nodes. (0 <= snr <= 255)\n\n"
+    "    reload_config [path]\n"
+    "      force reload wmediumd configuration file\n\n"
+    "      if path is not specified, reload current configuration file\n\n"
+    "    start_pcap path\n"
+    "      start packet capture and save capture result to file.\n"
+    "      file format is pcap capture format.\n\n"
+    "    stop_pcap\n"
+    "      stop packet capture\n\n"
+    "    list_stations\n"
+    "      listing stations connected to wmediumd\n\n";
+
+DEFINE_string(wmediumd_api_server, "",
+              "Unix socket path of wmediumd api server");
+
+const int kMacAddrStringSize = 17;
+
+bool ValidMacAddr(const std::string& macAddr) {
+  if (macAddr.size() != kMacAddrStringSize) {
+    return false;
+  }
+
+  if (macAddr[2] != ':' || macAddr[5] != ':' || macAddr[8] != ':' ||
+      macAddr[11] != ':' || macAddr[14] != ':') {
+    return false;
+  }
+
+  for (int i = 0; i < kMacAddrStringSize; ++i) {
+    if ((i - 2) % 3 == 0) continue;
+    char c = macAddr[i];
+
+    if (isupper(c)) {
+      c = tolower(c);
+    }
+
+    if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) return false;
+  }
+
+  return true;
+}
+
+std::string MacToString(const char* macAddr) {
+  std::stringstream result;
+
+  for (int i = 0; i < ETH_ALEN; i++) {
+    result << std::setfill('0') << std::setw(2) << std::right << std::hex
+           << static_cast<int>(static_cast<uint8_t>(macAddr[i]));
+
+    if (i != 5) {
+      result << ":";
+    }
+  }
+
+  return result.str();
+}
+
+bool HandleSetSnrCommand(cuttlefish::WmediumdController& client,
+                         const std::vector<std::string>& args) {
+  if (args.size() != 4) {
+    LOG(ERROR) << "error: set_snr must provide 3 options";
+    return false;
+  }
+
+  if (!ValidMacAddr(args[1])) {
+    LOG(ERROR) << "error: invalid mac address " << args[1];
+    return false;
+  }
+
+  if (!ValidMacAddr(args[2])) {
+    LOG(ERROR) << "error: invalid mac address " << args[2];
+    return false;
+  }
+
+  uint8_t snr = 0;
+
+  auto parseResult =
+      android::base::ParseUint<decltype(snr)>(args[3].c_str(), &snr);
+
+  if (!parseResult) {
+    if (errno == EINVAL) {
+      LOG(ERROR) << "error: cannot parse snr: " << args[3];
+    } else if (errno == ERANGE) {
+      LOG(ERROR) << "error: snr exceeded range: " << args[3];
+    }
+
+    return false;
+  }
+
+  if (!client.SetSnr(args[1], args[2], snr)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool HandleReloadConfigCommand(cuttlefish::WmediumdController& client,
+                               const std::vector<std::string>& args) {
+  if (args.size() > 2) {
+    LOG(ERROR) << "error: reload_config must provide 0 or 1 option";
+    return false;
+  }
+
+  if (args.size() == 2) {
+    return client.ReloadConfig(args[1]);
+  } else {
+    return client.ReloadCurrentConfig();
+  }
+}
+
+bool HandleStartPcapCommand(cuttlefish::WmediumdController& client,
+                            const std::vector<std::string>& args) {
+  if (args.size() != 2) {
+    LOG(ERROR) << "error: you must provide only 1 option(path)";
+    return false;
+  }
+
+  return client.StartPcap(args[1]);
+}
+
+bool HandleStopPcapCommand(cuttlefish::WmediumdController& client,
+                           const std::vector<std::string>& args) {
+  if (args.size() != 1) {
+    LOG(ERROR) << "error: you must not provide option";
+    return false;
+  }
+
+  return client.StopPcap();
+}
+
+bool HandleListStationsCommand(cuttlefish::WmediumdController& client,
+                               const std::vector<std::string>& args) {
+  if (args.size() != 1) {
+    LOG(ERROR) << "error: you must not provide option";
+    return false;
+  }
+
+  auto result = client.GetStations();
+
+  if (!result) {
+    LOG(ERROR) << "error: failed to get stations";
+    return false;
+  }
+
+  auto stationList = result->GetStations();
+
+  std::cout << "Total stations : " << stationList.size() << std::endl
+            << std::endl;
+  std::cout << "Mac Address      "
+            << "\t"
+            << "X Pos"
+            << "\t"
+            << "Y Pos"
+            << "\t"
+            << "TX Power" << std::endl;
+
+  for (auto& station : stationList) {
+    std::cout << MacToString(station.addr) << "\t" << std::setprecision(1)
+              << std::fixed << station.x << "\t" << std::setprecision(1)
+              << std::fixed << station.y << "\t" << station.tx_power
+              << std::endl;
+  }
+
+  std::cout << std::endl;
+
+  return true;
+}
+
+int main(int argc, char** argv) {
+  gflags::SetUsageMessage(usageMessage);
+  gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+  std::vector<std::string> args;
+
+  for (int i = 1; i < argc; ++i) {
+    args.push_back(argv[i]);
+  }
+
+  if (args.size() == 0) {
+    LOG(ERROR) << "error: you must provide at least 1 argument";
+    gflags::ShowUsageWithFlags(argv[0]);
+    return -1;
+  }
+
+  std::string wmediumdApiServerPath(FLAGS_wmediumd_api_server);
+
+  if (wmediumdApiServerPath == "") {
+    const auto cuttlefishConfig = cuttlefish::CuttlefishConfig::Get();
+
+    if (!cuttlefishConfig) {
+      LOG(ERROR) << "error: cannot get global cuttlefish config";
+      return -1;
+    }
+
+    wmediumdApiServerPath = cuttlefishConfig->wmediumd_api_server_socket();
+  }
+
+  auto client = cuttlefish::WmediumdController::New(wmediumdApiServerPath);
+
+  if (!client) {
+    LOG(ERROR) << "error: cannot connect to " << wmediumdApiServerPath;
+    return -1;
+  }
+
+  auto commandMap =
+      std::unordered_map<std::string,
+                         std::function<bool(cuttlefish::WmediumdController&,
+                                            const std::vector<std::string>&)>>{{
+          {"set_snr", HandleSetSnrCommand},
+          {"reload_config", HandleReloadConfigCommand},
+          {"start_pcap", HandleStartPcapCommand},
+          {"stop_pcap", HandleStopPcapCommand},
+          {"list_stations", HandleListStationsCommand},
+      }};
+
+  if (commandMap.find(args[0]) == std::end(commandMap)) {
+    LOG(ERROR) << "error: command " << args[0] << " does not exist";
+    gflags::ShowUsageWithFlags(argv[0]);
+    return -1;
+  }
+
+  if (!commandMap[args[0]](*client, args)) {
+    LOG(ERROR) << "error: failed to execute command " << args[0];
+    return -1;
+  }
+
+  return 0;
+}
diff --git a/host/example_custom_actions/Android.bp b/host/example_custom_actions/Android.bp
index 36161f6..a213099 100644
--- a/host/example_custom_actions/Android.bp
+++ b/host/example_custom_actions/Android.bp
@@ -9,6 +9,7 @@
         "cuttlefish_buildhost_only",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "liblog",
         "libutils",
diff --git a/host/example_custom_actions/README.md b/host/example_custom_actions/README.md
index de3764d..44d4fae 100644
--- a/host/example_custom_actions/README.md
+++ b/host/example_custom_actions/README.md
@@ -2,11 +2,8 @@
 following build vars:
 
 ```
-SOONG_CONFIG_NAMESPACES += cvd
-SOONG_CONFIG_cvd += custom_action_config custom_action_servers
-
-SOONG_CONFIG_cvd_custom_action_config := cuttlefish_example_action_config.json
-SOONG_CONFIG_cvd_custom_action_servers += cuttlefish_example_action_server
+$(call soong_config_set, cvd, custom_action_config, cuttlefish_example_action_config.json)
+$(call soong_config_append, cvd, custom_action_servers, cuttlefish_example_action_server)
 ```
 
 See `device/google/cuttlefish/build/README.md` for more information.
diff --git a/host/frontend/adb_connector/Android.bp b/host/frontend/adb_connector/Android.bp
index d0927af..d866fd3 100644
--- a/host/frontend/adb_connector/Android.bp
+++ b/host/frontend/adb_connector/Android.bp
@@ -29,6 +29,7 @@
         "libjsoncpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
diff --git a/host/commands/adbshell/Android.bp b/host/frontend/operator_proxy/Android.bp
similarity index 85%
rename from host/commands/adbshell/Android.bp
rename to host/frontend/operator_proxy/Android.bp
index 883503e..7709f59 100644
--- a/host/commands/adbshell/Android.bp
+++ b/host/frontend/operator_proxy/Android.bp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2018 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -18,20 +18,22 @@
 }
 
 cc_binary_host {
-    name: "adbshell",
+    name: "operator_proxy",
     srcs: [
         "main.cpp",
     ],
-    cflags: [
-        "-D_XOPEN_SOURCE",
-    ],
     shared_libs: [
         "libbase",
-        "libcuttlefish_utils",
+        "liblog",
+        "libjsoncpp",
+        "libcuttlefish_fs",
     ],
     static_libs: [
+        "libgflags",
+        "libcuttlefish_utils",
         "libcuttlefish_host_config",
-        "libjsoncpp",
     ],
     defaults: ["cuttlefish_buildhost_only"],
 }
+
+
diff --git a/host/frontend/operator_proxy/main.cpp b/host/frontend/operator_proxy/main.cpp
new file mode 100644
index 0000000..d4b640c
--- /dev/null
+++ b/host/frontend/operator_proxy/main.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <signal.h>
+
+#include <android-base/logging.h>
+#include <gflags/gflags.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/socket2socket_proxy.h"
+#include "host/libs/config/logging.h"
+
+DEFINE_int32(server_port, 8443, "The port for the proxy server");
+DEFINE_int32(operator_port, 1443, "The port of the operator server to proxy");
+
+cuttlefish::SharedFD OpenConnection() {
+  auto conn =
+      cuttlefish::SharedFD::SocketLocalClient(FLAGS_operator_port, SOCK_STREAM);
+  if (!conn->IsOpen()) {
+    LOG(ERROR) << "Failed to connect to operator: " << conn->StrError();
+  }
+  return conn;
+}
+
+int main(int argc, char** argv) {
+  cuttlefish::DefaultSubprocessLogging(argv);
+  ::gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+  auto server =
+      cuttlefish::SharedFD::SocketLocalServer(FLAGS_server_port, SOCK_STREAM);
+  CHECK(server->IsOpen()) << "Error Creating proxy server: "
+                          << server->StrError();
+
+  signal(SIGPIPE, SIG_IGN);
+
+  cuttlefish::Proxy(server, OpenConnection);
+
+  return 0;
+}
diff --git a/host/frontend/vnc_server/Android.bp b/host/frontend/vnc_server/Android.bp
deleted file mode 100644
index 468590b..0000000
--- a/host/frontend/vnc_server/Android.bp
+++ /dev/null
@@ -1,59 +0,0 @@
-//
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_binary {
-    name: "vnc_server",
-    srcs: [
-        "blackboard.cpp",
-        "frame_buffer_watcher.cpp",
-        "jpeg_compressor.cpp",
-        "main.cpp",
-        "simulated_hw_composer.cpp",
-        "virtual_inputs.cpp",
-        "vnc_client_connection.cpp",
-        "vnc_server.cpp",
-    ],
-    shared_libs: [
-        "libcuttlefish_fs",
-        "libcuttlefish_utils",
-        "libbase",
-        "libjsoncpp",
-        "liblog",
-    ],
-    header_libs: [
-        "libcuttlefish_confui_host_headers",
-    ],
-    static_libs: [
-        "libcuttlefish_host_config",
-        "libcuttlefish_screen_connector",
-        "libcuttlefish_wayland_server",
-        "libcuttlefish_confui",
-        "libcuttlefish_confui_host",
-        "libft2.nodep",
-        "libteeui",
-        "libteeui_localization",
-        "libffi",
-        "libjpeg",
-        "libgflags",
-        "libwayland_crosvm_gpu_display_extension_server_protocols",
-        "libwayland_extension_server_protocols",
-        "libwayland_server",
-    ],
-    defaults: ["cuttlefish_host"],
-}
diff --git a/host/frontend/vnc_server/blackboard.cpp b/host/frontend/vnc_server/blackboard.cpp
deleted file mode 100644
index 91a8d1e..0000000
--- a/host/frontend/vnc_server/blackboard.cpp
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "host/frontend/vnc_server/blackboard.h"
-
-#include <algorithm>
-#include <utility>
-
-#include <gflags/gflags.h>
-#include <android-base/logging.h>
-#include "host/frontend/vnc_server/frame_buffer_watcher.h"
-
-DEFINE_bool(debug_blackboard, false,
-            "Turn on detailed logging for the blackboard");
-
-#define DLOG(LEVEL)                                 \
-  if (FLAGS_debug_blackboard) LOG(LEVEL)
-
-using cuttlefish::vnc::BlackBoard;
-using cuttlefish::vnc::Stripe;
-
-cuttlefish::vnc::SeqNumberVec cuttlefish::vnc::MakeSeqNumberVec() {
-  return SeqNumberVec(FrameBufferWatcher::StripesPerFrame());
-}
-
-void BlackBoard::NewStripeReady(int index, StripeSeqNumber seq_num) {
-  std::lock_guard<std::mutex> guard(m_);
-  DLOG(INFO) << "new stripe arrived from frame watcher";
-  auto& current_seq_num = most_recent_stripe_seq_nums_[index];
-  current_seq_num = std::max(current_seq_num, seq_num);
-  for (auto& client : clients_) {
-    if (client.second.ready_to_receive) {
-      client.second.new_frame_cv.notify_one();
-    }
-  }
-}
-
-void BlackBoard::Register(const VncClientConnection* conn) {
-  {
-    std::lock_guard<std::mutex> guard(m_);
-    CHECK(!clients_.count(conn));
-    clients_[conn];  // constructs new state in place
-  }
-  new_client_cv_.notify_one();
-}
-
-void BlackBoard::Unregister(const VncClientConnection* conn) {
-  std::lock_guard<std::mutex> guard(m_);
-  CHECK(clients_.count(conn));
-  clients_.erase(clients_.find(conn));
-}
-
-bool BlackBoard::NoNewStripesFor(const SeqNumberVec& seq_nums) const {
-  CHECK(seq_nums.size() == most_recent_stripe_seq_nums_.size());
-  for (auto state_seq_num = seq_nums.begin(),
-            held_seq_num = most_recent_stripe_seq_nums_.begin();
-       state_seq_num != seq_nums.end(); ++state_seq_num, ++held_seq_num) {
-    if (*state_seq_num < *held_seq_num) {
-      return false;
-    }
-  }
-  return true;
-}
-
-cuttlefish::vnc::StripePtrVec BlackBoard::WaitForSenderWork(
-    const VncClientConnection* conn) {
-  std::unique_lock<std::mutex> guard(m_);
-  auto& state = GetStateForClient(conn);
-  DLOG(INFO) << "Waiting for stripe...";
-  while (!state.closed &&
-         (!state.ready_to_receive || NoNewStripesFor(state.stripe_seq_nums))) {
-    state.new_frame_cv.wait(guard);
-  }
-  DLOG(INFO) << "At least one new stripe is available, should unblock " << conn;
-  state.ready_to_receive = false;
-  auto new_stripes = frame_buffer_watcher_->StripesNewerThan(
-      state.orientation, state.stripe_seq_nums);
-  for (auto& s : new_stripes) {
-    state.stripe_seq_nums[s->index] = s->seq_number;
-  }
-  return new_stripes;
-}
-
-void BlackBoard::WaitForAtLeastOneClientConnection() {
-  std::unique_lock<std::mutex> guard(m_);
-  while (clients_.empty()) {
-    new_client_cv_.wait(guard);
-  }
-}
-
-void BlackBoard::SetOrientation(const VncClientConnection* conn,
-                                ScreenOrientation orientation) {
-  std::lock_guard<std::mutex> guard(m_);
-  auto& state = GetStateForClient(conn);
-  state.orientation = orientation;
-  // After an orientation change the vnc client will need all stripes from
-  // the new orientation, regardless of age.
-  ResetToZero(&state.stripe_seq_nums);
-}
-
-void BlackBoard::SignalClientNeedsEntireScreen(
-    const VncClientConnection* conn) {
-  std::lock_guard<std::mutex> guard(m_);
-  ResetToZero(&GetStateForClient(conn).stripe_seq_nums);
-}
-
-void BlackBoard::ResetToZero(SeqNumberVec* seq_nums) {
-  seq_nums->assign(FrameBufferWatcher::StripesPerFrame(), StripeSeqNumber{});
-}
-
-void BlackBoard::FrameBufferUpdateRequestReceived(
-    const VncClientConnection* conn) {
-  std::lock_guard<std::mutex> guard(m_);
-  DLOG(INFO) << "Received frame buffer update request";
-  auto& state = GetStateForClient(conn);
-  state.ready_to_receive = true;
-  state.new_frame_cv.notify_one();
-}
-
-void BlackBoard::StopWaiting(const VncClientConnection* conn) {
-  std::lock_guard<std::mutex> guard(m_);
-  auto& state = GetStateForClient(conn);
-  state.closed = true;
-  // Wake up the thread that might be in WaitForSenderWork()
-  state.new_frame_cv.notify_one();
-}
-
-void BlackBoard::set_frame_buffer_watcher(
-    cuttlefish::vnc::FrameBufferWatcher* frame_buffer_watcher) {
-  std::lock_guard<std::mutex> guard(m_);
-  frame_buffer_watcher_ = frame_buffer_watcher;
-}
-
-void BlackBoard::set_jpeg_quality_level(int quality_level) {
-  // NOTE all vnc clients share a common jpeg quality level because the
-  // server doesn't compress per-client. The quality level for all clients
-  // will be whatever the most recent set was by any client.
-  std::lock_guard<std::mutex> guard(m_);
-  if (quality_level < kJpegMinQualityEncoding ||
-      quality_level > kJpegMaxQualityEncoding) {
-    LOG(WARNING) << "Bogus jpeg quality level: " << quality_level
-                 << ". Quality must be in range [" << kJpegMinQualityEncoding
-                 << ", " << kJpegMaxQualityEncoding << "]";
-    return;
-  }
-  jpeg_quality_level_ = 55 + (5 * (quality_level + 32));
-  DLOG(INFO) << "jpeg quality level set to " << jpeg_quality_level_ << "%";
-}
-
-BlackBoard::ClientFBUState& BlackBoard::GetStateForClient(
-    const VncClientConnection* conn) {
-  CHECK(clients_.count(conn));
-  return clients_[conn];
-}
diff --git a/host/frontend/vnc_server/blackboard.h b/host/frontend/vnc_server/blackboard.h
deleted file mode 100644
index af4baea..0000000
--- a/host/frontend/vnc_server/blackboard.h
+++ /dev/null
@@ -1,113 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#include <condition_variable>
-#include <memory>
-#include <mutex>
-#include <unordered_map>
-
-#include "common/libs/concurrency/thread_annotations.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-
-namespace cuttlefish {
-namespace vnc {
-
-class VncClientConnection;
-class FrameBufferWatcher;
-using StripePtrVec = std::vector<std::shared_ptr<const Stripe>>;
-using SeqNumberVec = std::vector<StripeSeqNumber>;
-
-SeqNumberVec MakeSeqNumberVec();
-
-class BlackBoard {
- private:
-  struct ClientFBUState {
-    bool ready_to_receive{};
-    ScreenOrientation orientation{};
-    std::condition_variable new_frame_cv;
-    SeqNumberVec stripe_seq_nums = MakeSeqNumberVec();
-    bool closed{};
-  };
-
- public:
-  class Registerer {
-   public:
-    Registerer(BlackBoard* bb, const VncClientConnection* conn)
-        : bb_{bb}, conn_{conn} {
-      bb->Register(conn);
-    }
-    ~Registerer() { bb_->Unregister(conn_); }
-    Registerer(const Registerer&) = delete;
-    Registerer& operator=(const Registerer&) = delete;
-
-   private:
-    BlackBoard* bb_{};
-    const VncClientConnection* conn_{};
-  };
-
-  BlackBoard() = default;
-  BlackBoard(const BlackBoard&) = delete;
-  BlackBoard& operator=(const BlackBoard&) = delete;
-
-  bool NoNewStripesFor(const SeqNumberVec& seq_nums) const REQUIRES(m_);
-  void NewStripeReady(int index, StripeSeqNumber seq_num);
-  void Register(const VncClientConnection* conn);
-  void Unregister(const VncClientConnection* conn);
-
-  StripePtrVec WaitForSenderWork(const VncClientConnection* conn);
-
-  void WaitForAtLeastOneClientConnection();
-
-  void FrameBufferUpdateRequestReceived(const VncClientConnection* conn);
-  // Setting orientation implies needing the entire screen
-  void SetOrientation(const VncClientConnection* conn,
-                      ScreenOrientation orientation);
-  void SignalClientNeedsEntireScreen(const VncClientConnection* conn);
-
-  void StopWaiting(const VncClientConnection* conn);
-
-  void set_frame_buffer_watcher(FrameBufferWatcher* frame_buffer_watcher);
-
-  // quality_level must be the value received from the client, in the range
-  // [kJpegMinQualityEncoding, kJpegMaxQualityEncoding], else it is ignored.
-  void set_jpeg_quality_level(int quality_level);
-
-  int jpeg_quality_level() const {
-    std::lock_guard<std::mutex> guard(m_);
-    return jpeg_quality_level_;
-  }
-
- private:
-  ClientFBUState& GetStateForClient(const VncClientConnection* conn)
-      REQUIRES(m_);
-  static void ResetToZero(SeqNumberVec* seq_nums);
-
-  mutable std::mutex m_;
-  SeqNumberVec most_recent_stripe_seq_nums_ GUARDED_BY(m_) = MakeSeqNumberVec();
-  std::unordered_map<const VncClientConnection*, ClientFBUState> clients_
-      GUARDED_BY(m_);
-  int jpeg_quality_level_ GUARDED_BY(m_) = 100;
-  std::condition_variable new_client_cv_;
-  // NOTE the FrameBufferWatcher pointer itself should be
-  // guarded, but not the pointee.
-  FrameBufferWatcher* frame_buffer_watcher_ GUARDED_BY(m_){};
-};
-
-}  // namespace vnc
-}  // namespace cuttlefish
diff --git a/host/frontend/vnc_server/frame_buffer_watcher.cpp b/host/frontend/vnc_server/frame_buffer_watcher.cpp
deleted file mode 100644
index 3cc39c0..0000000
--- a/host/frontend/vnc_server/frame_buffer_watcher.cpp
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "host/frontend/vnc_server/frame_buffer_watcher.h"
-
-#include <algorithm>
-#include <cstdint>
-#include <cstring>
-#include <iterator>
-#include <memory>
-#include <mutex>
-#include <thread>
-#include <utility>
-
-#include <android-base/logging.h>
-#include "host/frontend/vnc_server/vnc_utils.h"
-
-using cuttlefish::vnc::FrameBufferWatcher;
-
-FrameBufferWatcher::FrameBufferWatcher(BlackBoard* bb,
-                                       ScreenConnector& screen_connector)
-    : bb_{bb}, hwcomposer{bb_, screen_connector} {
-  for (auto& stripes_vec : stripes_) {
-    std::generate_n(std::back_inserter(stripes_vec),
-                    SimulatedHWComposer::NumberOfStripes(),
-                    std::make_shared<Stripe>);
-  }
-  bb_->set_frame_buffer_watcher(this);
-  auto num_workers = std::max(std::thread::hardware_concurrency(), 1u);
-  std::generate_n(std::back_inserter(workers_), num_workers, [this] {
-    return std::thread{&FrameBufferWatcher::Worker, this};
-  });
-}
-
-FrameBufferWatcher::~FrameBufferWatcher() {
-  {
-    std::lock_guard<std::mutex> guard(m_);
-    closed_ = true;
-  }
-  for (auto& tid : workers_) {
-    tid.join();
-  }
-}
-
-bool FrameBufferWatcher::closed() const {
-  std::lock_guard<std::mutex> guard(m_);
-  return closed_;
-}
-
-cuttlefish::vnc::Stripe FrameBufferWatcher::Rotated(Stripe stripe) {
-  if (stripe.orientation == ScreenOrientation::Landscape) {
-    LOG(FATAL) << "Rotating a landscape stripe, this is a mistake";
-  }
-  auto w = stripe.width;
-  auto s = stripe.stride;
-  auto h = stripe.height;
-  const auto& raw = stripe.raw_data;
-  Message rotated(raw.size(), 0xAA);
-  for (std::uint16_t i = 0; i < w; ++i) {
-    for (std::uint16_t j = 0; j < h; ++j) {
-      size_t to = (i * h + j) * ScreenConnectorInfo::BytesPerPixel();
-      size_t from = (w - (i + 1)) * ScreenConnectorInfo::BytesPerPixel() + s * j;
-      CHECK(from < raw.size());
-      CHECK(to < rotated.size());
-      std::memcpy(&rotated[to], &raw[from], ScreenConnectorInfo::BytesPerPixel());
-    }
-  }
-  std::swap(stripe.x, stripe.y);
-  std::swap(stripe.width, stripe.height);
-  // The new stride after rotating is the height, as it is not aligned again.
-  stripe.stride = stripe.width * ScreenConnectorInfo::BytesPerPixel();
-  stripe.raw_data = std::move(rotated);
-  stripe.orientation = ScreenOrientation::Landscape;
-  return stripe;
-}
-
-bool FrameBufferWatcher::StripeIsDifferentFromPrevious(
-    const Stripe& stripe) const {
-  return Stripes(stripe.orientation)[stripe.index]->raw_data != stripe.raw_data;
-}
-
-cuttlefish::vnc::StripePtrVec FrameBufferWatcher::StripesNewerThan(
-    ScreenOrientation orientation, const SeqNumberVec& seq_numbers) const {
-  std::lock_guard<std::mutex> guard(stripes_lock_);
-  const auto& stripes = Stripes(orientation);
-  CHECK(seq_numbers.size() == stripes.size());
-  StripePtrVec new_stripes;
-  auto seq_number_it = seq_numbers.begin();
-  std::copy_if(stripes.begin(), stripes.end(), std::back_inserter(new_stripes),
-               [seq_number_it](const StripePtrVec::value_type& s) mutable {
-                 return *(seq_number_it++) < s->seq_number;
-               });
-  return new_stripes;
-}
-
-cuttlefish::vnc::StripePtrVec& FrameBufferWatcher::Stripes(
-    ScreenOrientation orientation) {
-  return stripes_[static_cast<int>(orientation)];
-}
-
-const cuttlefish::vnc::StripePtrVec& FrameBufferWatcher::Stripes(
-    ScreenOrientation orientation) const {
-  return stripes_[static_cast<int>(orientation)];
-}
-
-bool FrameBufferWatcher::UpdateMostRecentSeqNumIfStripeIsNew(
-    const Stripe& stripe) {
-  if (most_recent_identical_stripe_seq_nums_[stripe.index] <=
-      stripe.seq_number) {
-    most_recent_identical_stripe_seq_nums_[stripe.index] = stripe.seq_number;
-    return true;
-  }
-  return false;
-}
-
-bool FrameBufferWatcher::UpdateStripeIfStripeIsNew(
-    const std::shared_ptr<const Stripe>& stripe) {
-  std::lock_guard<std::mutex> guard(stripes_lock_);
-  if (UpdateMostRecentSeqNumIfStripeIsNew(*stripe)) {
-    Stripes(stripe->orientation)[stripe->index] = stripe;
-    return true;
-  }
-  return false;
-}
-
-void FrameBufferWatcher::CompressStripe(JpegCompressor* jpeg_compressor,
-                                        Stripe* stripe) {
-  stripe->jpeg_data = jpeg_compressor->Compress(
-      stripe->raw_data, bb_->jpeg_quality_level(), 0, 0, stripe->width,
-      stripe->height, stripe->stride);
-}
-
-void FrameBufferWatcher::Worker() {
-  JpegCompressor jpeg_compressor;
-#ifdef FUZZ_TEST_VNC
-  std::default_random_engine e{std::random_device{}()};
-  std::uniform_int_distribution<int> random{0, 2};
-#endif
-  while (!closed()) {
-    auto portrait_stripe = hwcomposer.GetNewStripe();
-    if (closed()) {
-      break;
-    }
-    {
-      // TODO(haining) use if (with init) and else for c++17 instead of extra
-      // scope and continue
-      // if (std::lock_guard guard(stripes_lock_); /*condition*/) { }
-      std::lock_guard<std::mutex> guard(stripes_lock_);
-      if (!StripeIsDifferentFromPrevious(portrait_stripe)) {
-        UpdateMostRecentSeqNumIfStripeIsNew(portrait_stripe);
-        continue;
-      }
-    }
-    auto seq_num = portrait_stripe.seq_number;
-    auto index = portrait_stripe.index;
-    auto landscape_stripe = Rotated(portrait_stripe);
-    auto stripes = {std::make_shared<Stripe>(std::move(portrait_stripe)),
-                    std::make_shared<Stripe>(std::move(landscape_stripe))};
-    for (auto& stripe : stripes) {
-#ifdef FUZZ_TEST_VNC
-      if (random(e)) {
-        usleep(10000);
-      }
-#endif
-      CompressStripe(&jpeg_compressor, stripe.get());
-    }
-    bool any_new_stripes = false;
-    for (auto& stripe : stripes) {
-      any_new_stripes = UpdateStripeIfStripeIsNew(stripe) || any_new_stripes;
-    }
-    if (any_new_stripes) {
-      bb_->NewStripeReady(index, seq_num);
-    }
-  }
-}
-
-int FrameBufferWatcher::StripesPerFrame() {
-  return SimulatedHWComposer::NumberOfStripes();
-}
-
-void FrameBufferWatcher::IncClientCount() {
-  hwcomposer.ReportClientsConnected();
-}
-
-void FrameBufferWatcher::DecClientCount() {
-  // Do nothing
-}
diff --git a/host/frontend/vnc_server/frame_buffer_watcher.h b/host/frontend/vnc_server/frame_buffer_watcher.h
deleted file mode 100644
index 9b559b6..0000000
--- a/host/frontend/vnc_server/frame_buffer_watcher.h
+++ /dev/null
@@ -1,81 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <memory>
-#include <mutex>
-#include <thread>
-#include <utility>
-#include <vector>
-
-#include "common/libs/concurrency/thread_annotations.h"
-#include "host/frontend/vnc_server/blackboard.h"
-#include "host/frontend/vnc_server/jpeg_compressor.h"
-#include "host/frontend/vnc_server/simulated_hw_composer.h"
-#include "host/libs/screen_connector/screen_connector.h"
-
-namespace cuttlefish {
-namespace vnc {
-class FrameBufferWatcher {
- public:
-  explicit FrameBufferWatcher(BlackBoard* bb,
-                              ScreenConnector& screen_connector);
-  FrameBufferWatcher(const FrameBufferWatcher&) = delete;
-  FrameBufferWatcher& operator=(const FrameBufferWatcher&) = delete;
-  ~FrameBufferWatcher();
-
-  StripePtrVec StripesNewerThan(ScreenOrientation orientation,
-                                const SeqNumberVec& seq_num) const;
-  void IncClientCount();
-  void DecClientCount();
-
-  static int StripesPerFrame();
-
- private:
-  static Stripe Rotated(Stripe stripe);
-
-  bool closed() const;
-  bool StripeIsDifferentFromPrevious(const Stripe& stripe) const
-      REQUIRES(stripes_lock_);
-  // returns true if stripe is still considered new and seq number was updated
-  bool UpdateMostRecentSeqNumIfStripeIsNew(const Stripe& stripe)
-      REQUIRES(stripes_lock_);
-  // returns true if stripe is still considered new and was updated
-  bool UpdateStripeIfStripeIsNew(const std::shared_ptr<const Stripe>& stripe)
-      EXCLUDES(stripes_lock_);
-  // Compresses stripe->raw_data to stripe->jpeg_data
-  void CompressStripe(JpegCompressor* jpeg_compressor, Stripe* stripe);
-  void Worker();
-  void Updater();
-
-  StripePtrVec& Stripes(ScreenOrientation orientation) REQUIRES(stripes_lock_);
-  const StripePtrVec& Stripes(ScreenOrientation orientation) const
-      REQUIRES(stripes_lock_);
-
-  std::vector<std::thread> workers_;
-  mutable std::mutex stripes_lock_;
-  std::array<StripePtrVec, kNumOrientations> stripes_ GUARDED_BY(stripes_lock_);
-  SeqNumberVec most_recent_identical_stripe_seq_nums_
-      GUARDED_BY(stripes_lock_) = MakeSeqNumberVec();
-  mutable std::mutex m_;
-  bool closed_ GUARDED_BY(m_){};
-  BlackBoard* bb_{};
-  SimulatedHWComposer hwcomposer;
-};
-
-}  // namespace vnc
-}  // namespace cuttlefish
diff --git a/host/frontend/vnc_server/jpeg_compressor.cpp b/host/frontend/vnc_server/jpeg_compressor.cpp
deleted file mode 100644
index 05013eb..0000000
--- a/host/frontend/vnc_server/jpeg_compressor.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h>  // stdio.h must appear before jpeglib.h
-#include <jpeglib.h>
-
-#include <android-base/logging.h>
-#include "host/frontend/vnc_server/jpeg_compressor.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/screen_connector/screen_connector.h"
-
-using cuttlefish::vnc::JpegCompressor;
-
-namespace {
-void InitCinfo(jpeg_compress_struct* cinfo, jpeg_error_mgr* err,
-               std::uint16_t width, std::uint16_t height, int jpeg_quality) {
-  cinfo->err = jpeg_std_error(err);
-  jpeg_create_compress(cinfo);
-
-  cinfo->image_width = width;
-  cinfo->image_height = height;
-  cinfo->input_components = cuttlefish::ScreenConnectorInfo::BytesPerPixel();
-  cinfo->in_color_space = JCS_EXT_RGBX;
-
-  jpeg_set_defaults(cinfo);
-  jpeg_set_quality(cinfo, jpeg_quality, true);
-}
-}  // namespace
-
-cuttlefish::Message JpegCompressor::Compress(const Message& frame,
-                                      int jpeg_quality, std::uint16_t x,
-                                      std::uint16_t y, std::uint16_t width,
-                                      std::uint16_t height,
-                                      int stride) {
-  jpeg_compress_struct cinfo{};
-  jpeg_error_mgr err{};
-  InitCinfo(&cinfo, &err, width, height, jpeg_quality);
-
-  auto* compression_buffer = buffer_.get();
-  auto compression_buffer_size = buffer_capacity_;
-  jpeg_mem_dest(&cinfo, &compression_buffer, &compression_buffer_size);
-  jpeg_start_compress(&cinfo, true);
-
-  while (cinfo.next_scanline < cinfo.image_height) {
-    auto row = static_cast<JSAMPROW>(const_cast<std::uint8_t*>(
-        &frame[(y * stride) +
-               (cinfo.next_scanline * stride) +
-               (x * cuttlefish::ScreenConnectorInfo::BytesPerPixel())]));
-    jpeg_write_scanlines(&cinfo, &row, 1);
-  }
-  jpeg_finish_compress(&cinfo);
-  jpeg_destroy_compress(&cinfo);
-
-  UpdateBuffer(compression_buffer, compression_buffer_size);
-  return {compression_buffer, compression_buffer + compression_buffer_size};
-}
-
-void JpegCompressor::UpdateBuffer(std::uint8_t* compression_buffer,
-                                  unsigned long compression_buffer_size) {
-  if (buffer_.get() != compression_buffer) {
-    buffer_capacity_ = compression_buffer_size;
-    buffer_.reset(compression_buffer);
-  }
-}
diff --git a/host/frontend/vnc_server/jpeg_compressor.h b/host/frontend/vnc_server/jpeg_compressor.h
deleted file mode 100644
index b6ef487..0000000
--- a/host/frontend/vnc_server/jpeg_compressor.h
+++ /dev/null
@@ -1,52 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cstdint>
-#include <cstdlib>
-#include <memory>
-
-#include "host/frontend/vnc_server/vnc_utils.h"
-
-namespace cuttlefish {
-namespace vnc {
-
-// libjpeg-turbo with jpeg_mem_dest (using memory as a destination) is funky.
-// If you give it a buffer that is big enough it will use it.
-// If you give it a buffer that is too small, it will allocate a new buffer
-// but will NOT free the buffer you gave it.
-// This class keeps track of the capacity of the working buffer, and frees the
-// old buffer if libjpeg-turbo silently discards it.
-class JpegCompressor {
- public:
-  Message Compress(const Message& frame, int jpeg_quality, std::uint16_t x,
-                   std::uint16_t y, std::uint16_t width, std::uint16_t height,
-                   int screen_width);
-
- private:
-  void UpdateBuffer(std::uint8_t* compression_buffer,
-                    unsigned long compression_buffer_size);
-  struct Freer {
-    void operator()(void* p) const { std::free(p); }
-  };
-
-  std::unique_ptr<std::uint8_t, Freer> buffer_;
-  unsigned long buffer_capacity_{};
-};
-
-}  // namespace vnc
-}  // namespace cuttlefish
diff --git a/host/frontend/vnc_server/keysyms.h b/host/frontend/vnc_server/keysyms.h
deleted file mode 100644
index ddcc0e4..0000000
--- a/host/frontend/vnc_server/keysyms.h
+++ /dev/null
@@ -1,54 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cstdint>
-
-namespace cuttlefish {
-namespace xk {
-
-constexpr uint32_t BackSpace = 0xff08, Tab = 0xff09, Return = 0xff0d,
-                   Enter = Return, Escape = 0xff1b, MultiKey = 0xff20,
-                   Insert = 0xff63, Delete = 0xffff, Pause = 0xff13,
-                   Home = 0xff50, End = 0xff57, PageUp = 0xff55,
-                   PageDown = 0xff56, Left = 0xff51, Up = 0xff52,
-                   Right = 0xff53, Down = 0xff54, F1 = 0xffbe, F2 = 0xffbf,
-                   F3 = 0xffc0, F4 = 0xffc1, F5 = 0xffc2, F6 = 0xffc3,
-                   F7 = 0xffc4, F8 = 0xffc5, F9 = 0xffc6, F10 = 0xffc7,
-                   F11 = 0xffc8, F12 = 0xffc9, F13 = 0xffca, F14 = 0xffcb,
-                   F15 = 0xffcc, F16 = 0xffcd, F17 = 0xffce, F18 = 0xffcf,
-                   F19 = 0xffd0, F20 = 0xffd1, F21 = 0xffd2, F22 = 0xffd3,
-                   F23 = 0xffd4, F24 = 0xffd5, ShiftLeft = 0xffe1,
-                   ShiftRight = 0xffe2, ControlLeft = 0xffe3,
-                   ControlRight = 0xffe4, MetaLeft = 0xffe7, MetaRight = 0xffe8,
-                   AltLeft = 0xffe9, AltRight = 0xffea, CapsLock = 0xffe5,
-                   NumLock = 0xff7f, ScrollLock = 0xff14, Keypad0 = 0xffb0,
-                   Keypad1 = 0xffb1, Keypad2 = 0xffb2, Keypad3 = 0xffb3,
-                   Keypad4 = 0xffb4, Keypad5 = 0xffb5, Keypad6 = 0xffb6,
-                   Keypad7 = 0xffb7, Keypad8 = 0xffb8, Keypad9 = 0xffb9,
-                   KeypadMultiply = 0xffaa, KeypadSubtract = 0xffad,
-                   KeypadAdd = 0xffab, KeypadDecimal = 0xffae,
-                   KeypadEnter = 0xff8d, KeypadDivide = 0xffaf,
-                   KeypadEqual = 0xffbd, PlusMinus = 0xb1, SysReq = 0xff15,
-                   LineFeed = 0xff0a, KeypadSeparator = 0xffac, Yen = 0xa5,
-                   Cancel = 0xff69, Undo = 0xff65, Redo = 0xff66, Find = 0xff68,
-                   Print = 0xff61, VolumeDown = 0x1008ff11, Mute = 0x1008ff12,
-                   VolumeUp = 0x1008ff13, Menu = 0xff67,
-                   VNCMenu = 0xffed;  // VNC seems to translate MENU to this
-
-}  // namespace xk
-}  // namespace cuttlefish
diff --git a/host/frontend/vnc_server/main.cpp b/host/frontend/vnc_server/main.cpp
deleted file mode 100644
index e036b40..0000000
--- a/host/frontend/vnc_server/main.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <algorithm>
-#include <memory>
-#include <string>
-
-#include <gflags/gflags.h>
-
-#include "host/frontend/vnc_server/simulated_hw_composer.h"
-#include "host/frontend/vnc_server/vnc_server.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/config/logging.h"
-#include "host/libs/confui/host_mode_ctrl.h"
-#include "host/libs/confui/host_server.h"
-
-DEFINE_bool(agressive, false, "Whether to use agressive server");
-DEFINE_int32(frame_server_fd, -1, "");
-DEFINE_int32(port, 6444, "Port where to listen for connections");
-
-int main(int argc, char* argv[]) {
-  cuttlefish::DefaultSubprocessLogging(argv);
-  google::ParseCommandLineFlags(&argc, &argv, true);
-
-  auto& host_mode_ctrl = cuttlefish::HostModeCtrl::Get();
-  auto screen_connector_ptr = cuttlefish::vnc::ScreenConnector::Get(
-      FLAGS_frame_server_fd, host_mode_ctrl);
-  auto& screen_connector = *(screen_connector_ptr.get());
-
-  // create confirmation UI service, giving host_mode_ctrl and
-  // screen_connector
-  // keep this singleton object alive until the webRTC process ends
-  static auto& host_confui_server =
-      cuttlefish::confui::HostServer::Get(host_mode_ctrl, screen_connector);
-
-  host_confui_server.Start();
-  // lint does not like the spelling of "agressive", so needs NOTYPO
-  cuttlefish::vnc::VncServer vnc_server(FLAGS_port, FLAGS_agressive,  // NOTYPO
-                                        screen_connector, host_confui_server);
-  vnc_server.MainLoop();
-}
diff --git a/host/frontend/vnc_server/mocks.h b/host/frontend/vnc_server/mocks.h
deleted file mode 100644
index e69eb43..0000000
--- a/host/frontend/vnc_server/mocks.h
+++ /dev/null
@@ -1,35 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-struct GceFrameBuffer {
-  typedef uint32_t Pixel;
-
-  static const int kRedShift = 0;
-  static const int kRedBits = 8;
-  static const int kGreenShift = 8;
-  static const int kGreenBits = 8;
-  static const int kBlueShift = 16;
-  static const int kBlueBits = 8;
-  static const int kAlphaShift = 24;
-  static const int kAlphaBits = 8;
-};
-
-// Sensors
-struct gce_sensors_message {
-  static constexpr const char* const kSensorsHALSocketName = "";
-};
diff --git a/host/frontend/vnc_server/simulated_hw_composer.cpp b/host/frontend/vnc_server/simulated_hw_composer.cpp
deleted file mode 100644
index a02ae4b..0000000
--- a/host/frontend/vnc_server/simulated_hw_composer.cpp
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "host/frontend/vnc_server/simulated_hw_composer.h"
-
-#include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/config/cuttlefish_config.h"
-
-using cuttlefish::vnc::SimulatedHWComposer;
-using ScreenConnector = cuttlefish::vnc::ScreenConnector;
-
-SimulatedHWComposer::SimulatedHWComposer(BlackBoard* bb,
-                                         ScreenConnector& screen_connector)
-    :
-#ifdef FUZZ_TEST_VNC
-      engine_{std::random_device{}()},
-#endif
-      bb_{bb},
-      stripes_(kMaxQueueElements, &SimulatedHWComposer::EraseHalfOfElements),
-      screen_connector_(screen_connector) {
-  stripe_maker_ = std::thread(&SimulatedHWComposer::MakeStripes, this);
-  screen_connector_.SetCallback(std::move(GetScreenConnectorCallback()));
-}
-
-SimulatedHWComposer::~SimulatedHWComposer() {
-  close();
-  stripe_maker_.join();
-}
-
-cuttlefish::vnc::Stripe SimulatedHWComposer::GetNewStripe() {
-  auto s = stripes_.Pop();
-#ifdef FUZZ_TEST_VNC
-  if (random_(engine_)) {
-    usleep(7000);
-    stripes_.Push(std::move(s));
-    s = stripes_.Pop();
-  }
-#endif
-  return s;
-}
-
-bool SimulatedHWComposer::closed() {
-  std::lock_guard<std::mutex> guard(m_);
-  return closed_;
-}
-
-void SimulatedHWComposer::close() {
-  std::lock_guard<std::mutex> guard(m_);
-  closed_ = true;
-}
-
-// Assuming the number of stripes is less than half the size of the queue
-// this will be safe as the newest stripes won't be lost. In the real
-// hwcomposer, where stripes are coming in a different order, the full
-// queue case would probably need a different approach to be safe.
-void SimulatedHWComposer::EraseHalfOfElements(
-    ThreadSafeQueue<Stripe>::QueueImpl* q) {
-  q->erase(q->begin(), std::next(q->begin(), kMaxQueueElements / 2));
-}
-
-SimulatedHWComposer::GenerateProcessedFrameCallback
-SimulatedHWComposer::GetScreenConnectorCallback() {
-  return [](std::uint32_t display_number, std::uint32_t frame_w,
-            std::uint32_t frame_h, std::uint32_t frame_stride_bytes,
-            std::uint8_t* frame_bytes,
-            cuttlefish::vnc::VncScProcessedFrame& processed_frame) {
-    processed_frame.display_number_ = display_number;
-    // TODO(171305898): handle multiple displays.
-    if (display_number != 0) {
-      // BUG 186580833: display_number comes from surface_id in crosvm
-      // create_surface from virtio_gpu.rs set_scanout.  We cannot use it as
-      // the display number. Either crosvm virtio-gpu is incorrectly ignoring
-      // scanout id and instead using a monotonically increasing surface id
-      // number as the scanout resource is replaced over time, or frontend code
-      // here is incorrectly assuming  surface id == display id.
-      display_number = 0;
-    }
-
-    const std::uint32_t frame_bpp = 4;
-    const std::uint32_t frame_size_bytes = frame_h * frame_stride_bytes;
-
-    auto& raw_screen = processed_frame.raw_screen_;
-    raw_screen.assign(frame_bytes, frame_bytes + frame_size_bytes);
-
-    static std::uint32_t next_frame_number = 0;
-
-    const auto num_stripes = SimulatedHWComposer::kNumStripes;
-    for (int i = 0; i < num_stripes; ++i) {
-      std::uint16_t y = (frame_h / num_stripes) * i;
-
-      // Last frames on the right and/or bottom handle extra pixels
-      // when a screen dimension is not evenly divisible by Frame::kNumSlots.
-      std::uint16_t height = frame_h / num_stripes +
-                             (i + 1 == num_stripes ? frame_h % num_stripes : 0);
-      const auto* raw_start = &raw_screen[y * frame_w * frame_bpp];
-      const auto* raw_end = raw_start + (height * frame_w * frame_bpp);
-      // creating a named object and setting individual data members in order
-      // to make klp happy
-      // TODO (haining) construct this inside the call when not compiling
-      // on klp
-      Stripe s{};
-      s.index = i;
-      s.x = 0;
-      s.y = y;
-      s.width = frame_w;
-      s.stride = frame_stride_bytes;
-      s.height = height;
-      s.frame_id = next_frame_number++;
-      s.raw_data.assign(raw_start, raw_end);
-      s.orientation = ScreenOrientation::Portrait;
-      processed_frame.stripes_.push_back(std::move(s));
-    }
-
-    processed_frame.display_number_ = display_number;
-    processed_frame.is_success_ = true;
-  };
-}
-
-void SimulatedHWComposer::MakeStripes() {
-  std::uint64_t stripe_seq_num = 1;
-  /*
-   * callback should be set before the first WaitForAtLeastOneClientConnection()
-   * (b/178504150) and the first OnFrameAfter().
-   */
-  if (!screen_connector_.IsCallbackSet()) {
-    LOG(FATAL) << "ScreenConnector callback hasn't been set before MakeStripes";
-  }
-  while (!closed()) {
-    bb_->WaitForAtLeastOneClientConnection();
-    auto sim_hw_processed_frame = screen_connector_.OnNextFrame();
-    // sim_hw_processed_frame has display number from the guest
-    if (!sim_hw_processed_frame.is_success_) {
-      continue;
-    }
-    while (!sim_hw_processed_frame.stripes_.empty()) {
-      /*
-       * ScreenConnector that supplies the frames into the queue
-       * cannot be aware of stripe_seq_num. The callback was set at the
-       * ScreenConnector creation time. ScreenConnector calls the callback
-       * function autonomously to make the processed frames to supply the
-       * queue with.
-       *
-       * Besides, ScreenConnector is not VNC specific. Thus, stripe_seq_num,
-       * a VNC specific information, is maintained here.
-       *
-       * OnFrameAfter returns a sim_hw_processed_frame, that contains N consecutive stripes.
-       * each stripe s has an invalid seq_number, default-initialzed
-       * We set the field properly, and push to the stripes_
-       */
-      auto& s = sim_hw_processed_frame.stripes_.front();
-      stripe_seq_num++;
-      s.seq_number = StripeSeqNumber{stripe_seq_num};
-      stripes_.Push(std::move(s));
-      sim_hw_processed_frame.stripes_.pop_front();
-    }
-  }
-}
-
-int SimulatedHWComposer::NumberOfStripes() { return kNumStripes; }
-
-void SimulatedHWComposer::ReportClientsConnected() {
-  screen_connector_.ReportClientsConnected(true);
-}
diff --git a/host/frontend/vnc_server/simulated_hw_composer.h b/host/frontend/vnc_server/simulated_hw_composer.h
deleted file mode 100644
index 9d62e3e..0000000
--- a/host/frontend/vnc_server/simulated_hw_composer.h
+++ /dev/null
@@ -1,74 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <condition_variable>
-#include <mutex>
-#ifdef FUZZ_TEST_VNC
-#include <random>
-#endif
-#include <thread>
-#include <deque>
-
-#include "common/libs/concurrency/thread_annotations.h"
-#include "common/libs/concurrency/thread_safe_queue.h"
-#include "host/frontend/vnc_server/blackboard.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/screen_connector/screen_connector.h"
-
-namespace cuttlefish {
-namespace vnc {
-class SimulatedHWComposer {
- public:
-  using GenerateProcessedFrameCallback = ScreenConnector::GenerateProcessedFrameCallback;
-
-  SimulatedHWComposer(BlackBoard* bb, ScreenConnector& screen_connector);
-  SimulatedHWComposer(const SimulatedHWComposer&) = delete;
-  SimulatedHWComposer& operator=(const SimulatedHWComposer&) = delete;
-  ~SimulatedHWComposer();
-
-  Stripe GetNewStripe();
-
-  void ReportClientsConnected();
-
-  // NOTE not constexpr on purpose
-  static int NumberOfStripes();
-
- private:
-  bool closed();
-  void close();
-  static void EraseHalfOfElements(ThreadSafeQueue<Stripe>::QueueImpl* q);
-  void MakeStripes();
-  GenerateProcessedFrameCallback GetScreenConnectorCallback();
-
-#ifdef FUZZ_TEST_VNC
-  std::default_random_engine engine_;
-  std::uniform_int_distribution<int> random_ =
-      std::uniform_int_distribution<int>{0, 2};
-#endif
-  static constexpr int kNumStripes = 8;
-  constexpr static std::size_t kMaxQueueElements = 64;
-  bool closed_ GUARDED_BY(m_){};
-  std::mutex m_;
-  BlackBoard* bb_{};
-  ThreadSafeQueue<Stripe> stripes_;
-  std::thread stripe_maker_;
-  ScreenConnector& screen_connector_;
-};
-}  // namespace vnc
-}  // namespace cuttlefish
diff --git a/host/frontend/vnc_server/virtual_inputs.cpp b/host/frontend/vnc_server/virtual_inputs.cpp
deleted file mode 100644
index 4876338..0000000
--- a/host/frontend/vnc_server/virtual_inputs.cpp
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "host/frontend/vnc_server/virtual_inputs.h"
-
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <gflags/gflags.h>
-#include <linux/input.h>
-#include <linux/uinput.h>
-
-#include <cstdint>
-#include <mutex>
-#include <thread>
-#include "keysyms.h"
-
-#include "common/libs/confui/confui.h"
-#include "common/libs/fs/shared_select.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/config/logging.h"
-
-using cuttlefish::vnc::VirtualInputs;
-
-DEFINE_string(touch_fds, "",
-              "A list of fds for sockets where to accept touch connections");
-
-DEFINE_int32(keyboard_fd, -1,
-             "A fd for a socket where to accept keyboard connections");
-
-DEFINE_bool(write_virtio_input, false,
-            "Whether to write the virtio_input struct over the socket");
-
-namespace {
-// Necessary to define here as the virtio_input.h header is not available
-// in the host glibc.
-struct virtio_input_event {
-  std::uint16_t type;
-  std::uint16_t code;
-  std::int32_t value;
-};
-
-void AddKeyMappings(std::map<uint32_t, uint16_t>* key_mapping) {
-  (*key_mapping)[cuttlefish::xk::AltLeft] = KEY_LEFTALT;
-  (*key_mapping)[cuttlefish::xk::ControlLeft] = KEY_LEFTCTRL;
-  (*key_mapping)[cuttlefish::xk::ShiftLeft] = KEY_LEFTSHIFT;
-  (*key_mapping)[cuttlefish::xk::AltRight] = KEY_RIGHTALT;
-  (*key_mapping)[cuttlefish::xk::ControlRight] = KEY_RIGHTCTRL;
-  (*key_mapping)[cuttlefish::xk::ShiftRight] = KEY_RIGHTSHIFT;
-  (*key_mapping)[cuttlefish::xk::MetaLeft] = KEY_LEFTMETA;
-  (*key_mapping)[cuttlefish::xk::MetaRight] = KEY_RIGHTMETA;
-  (*key_mapping)[cuttlefish::xk::MultiKey] = KEY_COMPOSE;
-
-  (*key_mapping)[cuttlefish::xk::CapsLock] = KEY_CAPSLOCK;
-  (*key_mapping)[cuttlefish::xk::NumLock] = KEY_NUMLOCK;
-  (*key_mapping)[cuttlefish::xk::ScrollLock] = KEY_SCROLLLOCK;
-
-  (*key_mapping)[cuttlefish::xk::BackSpace] = KEY_BACKSPACE;
-  (*key_mapping)[cuttlefish::xk::Tab] = KEY_TAB;
-  (*key_mapping)[cuttlefish::xk::Return] = KEY_ENTER;
-  (*key_mapping)[cuttlefish::xk::Escape] = KEY_ESC;
-
-  (*key_mapping)[' '] = KEY_SPACE;
-  (*key_mapping)['!'] = KEY_1;
-  (*key_mapping)['"'] = KEY_APOSTROPHE;
-  (*key_mapping)['#'] = KEY_3;
-  (*key_mapping)['$'] = KEY_4;
-  (*key_mapping)['%'] = KEY_5;
-  (*key_mapping)['^'] = KEY_6;
-  (*key_mapping)['&'] = KEY_7;
-  (*key_mapping)['\''] = KEY_APOSTROPHE;
-  (*key_mapping)['('] = KEY_9;
-  (*key_mapping)[')'] = KEY_0;
-  (*key_mapping)['*'] = KEY_8;
-  (*key_mapping)['+'] = KEY_EQUAL;
-  (*key_mapping)[','] = KEY_COMMA;
-  (*key_mapping)['-'] = KEY_MINUS;
-  (*key_mapping)['.'] = KEY_DOT;
-  (*key_mapping)['/'] = KEY_SLASH;
-  (*key_mapping)['0'] = KEY_0;
-  (*key_mapping)['1'] = KEY_1;
-  (*key_mapping)['2'] = KEY_2;
-  (*key_mapping)['3'] = KEY_3;
-  (*key_mapping)['4'] = KEY_4;
-  (*key_mapping)['5'] = KEY_5;
-  (*key_mapping)['6'] = KEY_6;
-  (*key_mapping)['7'] = KEY_7;
-  (*key_mapping)['8'] = KEY_8;
-  (*key_mapping)['9'] = KEY_9;
-  (*key_mapping)[':'] = KEY_SEMICOLON;
-  (*key_mapping)[';'] = KEY_SEMICOLON;
-  (*key_mapping)['<'] = KEY_COMMA;
-  (*key_mapping)['='] = KEY_EQUAL;
-  (*key_mapping)['>'] = KEY_DOT;
-  (*key_mapping)['?'] = KEY_SLASH;
-  (*key_mapping)['@'] = KEY_2;
-  (*key_mapping)['A'] = KEY_A;
-  (*key_mapping)['B'] = KEY_B;
-  (*key_mapping)['C'] = KEY_C;
-  (*key_mapping)['D'] = KEY_D;
-  (*key_mapping)['E'] = KEY_E;
-  (*key_mapping)['F'] = KEY_F;
-  (*key_mapping)['G'] = KEY_G;
-  (*key_mapping)['H'] = KEY_H;
-  (*key_mapping)['I'] = KEY_I;
-  (*key_mapping)['J'] = KEY_J;
-  (*key_mapping)['K'] = KEY_K;
-  (*key_mapping)['L'] = KEY_L;
-  (*key_mapping)['M'] = KEY_M;
-  (*key_mapping)['N'] = KEY_N;
-  (*key_mapping)['O'] = KEY_O;
-  (*key_mapping)['P'] = KEY_P;
-  (*key_mapping)['Q'] = KEY_Q;
-  (*key_mapping)['R'] = KEY_R;
-  (*key_mapping)['S'] = KEY_S;
-  (*key_mapping)['T'] = KEY_T;
-  (*key_mapping)['U'] = KEY_U;
-  (*key_mapping)['V'] = KEY_V;
-  (*key_mapping)['W'] = KEY_W;
-  (*key_mapping)['X'] = KEY_X;
-  (*key_mapping)['Y'] = KEY_Y;
-  (*key_mapping)['Z'] = KEY_Z;
-  (*key_mapping)['['] = KEY_LEFTBRACE;
-  (*key_mapping)['\\'] = KEY_BACKSLASH;
-  (*key_mapping)[']'] = KEY_RIGHTBRACE;
-  (*key_mapping)['-'] = KEY_MINUS;
-  (*key_mapping)['_'] = KEY_MINUS;
-  (*key_mapping)['`'] = KEY_GRAVE;
-  (*key_mapping)['a'] = KEY_A;
-  (*key_mapping)['b'] = KEY_B;
-  (*key_mapping)['c'] = KEY_C;
-  (*key_mapping)['d'] = KEY_D;
-  (*key_mapping)['e'] = KEY_E;
-  (*key_mapping)['f'] = KEY_F;
-  (*key_mapping)['g'] = KEY_G;
-  (*key_mapping)['h'] = KEY_H;
-  (*key_mapping)['i'] = KEY_I;
-  (*key_mapping)['j'] = KEY_J;
-  (*key_mapping)['k'] = KEY_K;
-  (*key_mapping)['l'] = KEY_L;
-  (*key_mapping)['m'] = KEY_M;
-  (*key_mapping)['n'] = KEY_N;
-  (*key_mapping)['o'] = KEY_O;
-  (*key_mapping)['p'] = KEY_P;
-  (*key_mapping)['q'] = KEY_Q;
-  (*key_mapping)['r'] = KEY_R;
-  (*key_mapping)['s'] = KEY_S;
-  (*key_mapping)['t'] = KEY_T;
-  (*key_mapping)['u'] = KEY_U;
-  (*key_mapping)['v'] = KEY_V;
-  (*key_mapping)['w'] = KEY_W;
-  (*key_mapping)['x'] = KEY_X;
-  (*key_mapping)['y'] = KEY_Y;
-  (*key_mapping)['z'] = KEY_Z;
-  (*key_mapping)['{'] = KEY_LEFTBRACE;
-  (*key_mapping)['\\'] = KEY_BACKSLASH;
-  (*key_mapping)['|'] = KEY_BACKSLASH;
-  (*key_mapping)['}'] = KEY_RIGHTBRACE;
-  (*key_mapping)['~'] = KEY_GRAVE;
-
-  (*key_mapping)[cuttlefish::xk::F1] = KEY_F1;
-  (*key_mapping)[cuttlefish::xk::F2] = KEY_F2;
-  (*key_mapping)[cuttlefish::xk::F3] = KEY_F3;
-  (*key_mapping)[cuttlefish::xk::F4] = KEY_F4;
-  (*key_mapping)[cuttlefish::xk::F5] = KEY_F5;
-  (*key_mapping)[cuttlefish::xk::F6] = KEY_F6;
-  (*key_mapping)[cuttlefish::xk::F7] = KEY_F7;
-  (*key_mapping)[cuttlefish::xk::F8] = KEY_F8;
-  (*key_mapping)[cuttlefish::xk::F9] = KEY_F9;
-  (*key_mapping)[cuttlefish::xk::F10] = KEY_F10;
-  (*key_mapping)[cuttlefish::xk::F11] = KEY_F11;
-  (*key_mapping)[cuttlefish::xk::F12] = KEY_F12;
-  (*key_mapping)[cuttlefish::xk::F13] = KEY_F13;
-  (*key_mapping)[cuttlefish::xk::F14] = KEY_F14;
-  (*key_mapping)[cuttlefish::xk::F15] = KEY_F15;
-  (*key_mapping)[cuttlefish::xk::F16] = KEY_F16;
-  (*key_mapping)[cuttlefish::xk::F17] = KEY_F17;
-  (*key_mapping)[cuttlefish::xk::F18] = KEY_F18;
-  (*key_mapping)[cuttlefish::xk::F19] = KEY_F19;
-  (*key_mapping)[cuttlefish::xk::F20] = KEY_F20;
-  (*key_mapping)[cuttlefish::xk::F21] = KEY_F21;
-  (*key_mapping)[cuttlefish::xk::F22] = KEY_F22;
-  (*key_mapping)[cuttlefish::xk::F23] = KEY_F23;
-  (*key_mapping)[cuttlefish::xk::F24] = KEY_F24;
-
-  (*key_mapping)[cuttlefish::xk::Keypad0] = KEY_KP0;
-  (*key_mapping)[cuttlefish::xk::Keypad1] = KEY_KP1;
-  (*key_mapping)[cuttlefish::xk::Keypad2] = KEY_KP2;
-  (*key_mapping)[cuttlefish::xk::Keypad3] = KEY_KP3;
-  (*key_mapping)[cuttlefish::xk::Keypad4] = KEY_KP4;
-  (*key_mapping)[cuttlefish::xk::Keypad5] = KEY_KP5;
-  (*key_mapping)[cuttlefish::xk::Keypad6] = KEY_KP6;
-  (*key_mapping)[cuttlefish::xk::Keypad7] = KEY_KP7;
-  (*key_mapping)[cuttlefish::xk::Keypad8] = KEY_KP8;
-  (*key_mapping)[cuttlefish::xk::Keypad9] = KEY_KP9;
-  (*key_mapping)[cuttlefish::xk::KeypadMultiply] = KEY_KPASTERISK;
-  (*key_mapping)[cuttlefish::xk::KeypadSubtract] = KEY_KPMINUS;
-  (*key_mapping)[cuttlefish::xk::KeypadAdd] = KEY_KPPLUS;
-  (*key_mapping)[cuttlefish::xk::KeypadDecimal] = KEY_KPDOT;
-  (*key_mapping)[cuttlefish::xk::KeypadEnter] = KEY_KPENTER;
-  (*key_mapping)[cuttlefish::xk::KeypadDivide] = KEY_KPSLASH;
-  (*key_mapping)[cuttlefish::xk::KeypadEqual] = KEY_KPEQUAL;
-  (*key_mapping)[cuttlefish::xk::PlusMinus] = KEY_KPPLUSMINUS;
-
-  (*key_mapping)[cuttlefish::xk::SysReq] = KEY_SYSRQ;
-  (*key_mapping)[cuttlefish::xk::LineFeed] = KEY_LINEFEED;
-  (*key_mapping)[cuttlefish::xk::Home] = KEY_HOME;
-  (*key_mapping)[cuttlefish::xk::Up] = KEY_UP;
-  (*key_mapping)[cuttlefish::xk::PageUp] = KEY_PAGEUP;
-  (*key_mapping)[cuttlefish::xk::Left] = KEY_LEFT;
-  (*key_mapping)[cuttlefish::xk::Right] = KEY_RIGHT;
-  (*key_mapping)[cuttlefish::xk::End] = KEY_END;
-  (*key_mapping)[cuttlefish::xk::Down] = KEY_DOWN;
-  (*key_mapping)[cuttlefish::xk::PageDown] = KEY_PAGEDOWN;
-  (*key_mapping)[cuttlefish::xk::Insert] = KEY_INSERT;
-  (*key_mapping)[cuttlefish::xk::Delete] = KEY_DELETE;
-  (*key_mapping)[cuttlefish::xk::Pause] = KEY_PAUSE;
-  (*key_mapping)[cuttlefish::xk::KeypadSeparator] = KEY_KPCOMMA;
-  (*key_mapping)[cuttlefish::xk::Yen] = KEY_YEN;
-  (*key_mapping)[cuttlefish::xk::Cancel] = KEY_STOP;
-  (*key_mapping)[cuttlefish::xk::Redo] = KEY_AGAIN;
-  (*key_mapping)[cuttlefish::xk::Undo] = KEY_UNDO;
-  (*key_mapping)[cuttlefish::xk::Find] = KEY_FIND;
-  (*key_mapping)[cuttlefish::xk::Print] = KEY_PRINT;
-  (*key_mapping)[cuttlefish::xk::VolumeDown] = KEY_VOLUMEDOWN;
-  (*key_mapping)[cuttlefish::xk::Mute] = KEY_MUTE;
-  (*key_mapping)[cuttlefish::xk::VolumeUp] = KEY_VOLUMEUP;
-  (*key_mapping)[cuttlefish::xk::Menu] = KEY_MENU;
-  (*key_mapping)[cuttlefish::xk::VNCMenu] = KEY_MENU;
-}
-
-void InitInputEvent(struct input_event* evt, uint16_t type, uint16_t code,
-                    int32_t value) {
-  evt->type = type;
-  evt->code = code;
-  evt->value = value;
-}
-
-}  // namespace
-
-class SocketVirtualInputs : public VirtualInputs {
- public:
-  SocketVirtualInputs()
-      : client_connector_([this]() { ClientConnectorLoop(); }) {}
-
-  void GenerateKeyPressEvent(int key_code, bool down) override {
-    struct input_event events[2];
-    InitInputEvent(&events[0], EV_KEY, keymapping_[key_code], down);
-    InitInputEvent(&events[1], EV_SYN, 0, 0);
-
-    SendEvents(keyboard_socket_, events);
-  }
-
-  void PressPowerButton(bool down) override {
-    struct input_event events[2];
-    InitInputEvent(&events[0], EV_KEY, KEY_POWER, down);
-    InitInputEvent(&events[1], EV_SYN, 0, 0);
-
-    SendEvents(keyboard_socket_, events);
-  }
-
-  void HandlePointerEvent(bool touch_down, int x, int y) override {
-    // TODO(b/124121375): Use multitouch when available
-    struct input_event events[4];
-    InitInputEvent(&events[0], EV_ABS, ABS_X, x);
-    InitInputEvent(&events[1], EV_ABS, ABS_Y, y);
-    InitInputEvent(&events[2], EV_KEY, BTN_TOUCH, touch_down);
-    InitInputEvent(&events[3], EV_SYN, 0, 0);
-
-    SendEvents(touch_socket_, events);
-  }
-
- private:
-  template<size_t num_events>
-  void SendEvents(cuttlefish::SharedFD socket, struct input_event (&event_buffer)[num_events]) {
-    std::lock_guard<std::mutex> lock(socket_mutex_);
-    if (!socket->IsOpen()) {
-      // This is unlikely as it would only happen between the start of the vnc
-      // server and the connection of the VMM to the socket.
-      // If it happens, just drop the events as the VM is not yet ready to
-      // handle it.
-      return;
-    }
-
-    if (FLAGS_write_virtio_input) {
-      struct virtio_input_event virtio_events[num_events];
-      for (size_t i = 0; i < num_events; i++) {
-        virtio_events[i] = (struct virtio_input_event) {
-          .type = event_buffer[i].type,
-          .code = event_buffer[i].code,
-          .value = event_buffer[i].value,
-        };
-      }
-      auto ret = socket->Write(virtio_events, sizeof(virtio_events));
-      if (ret < 0) {
-        LOG(ERROR) << "Error sending input events: " << socket->StrError();
-      }
-    } else {
-      auto ret = socket->Write(event_buffer, sizeof(event_buffer));
-      if (ret < 0) {
-        LOG(ERROR) << "Error sending input events: " << socket->StrError();
-      }
-    }
-  }
-
-  void ClientConnectorLoop() {
-    auto touch_fd =
-        std::stoi(android::base::Split(FLAGS_touch_fds, ",").front());
-    auto touch_server = cuttlefish::SharedFD::Dup(touch_fd);
-    close(touch_fd);
-
-    auto keyboard_server = cuttlefish::SharedFD::Dup(FLAGS_keyboard_fd);
-    close(FLAGS_keyboard_fd);
-    FLAGS_keyboard_fd = -1;
-    LOG(DEBUG) << "Input socket host accepting connections...";
-
-    while (1) {
-      cuttlefish::SharedFDSet read_set;
-      read_set.Set(touch_server);
-      read_set.Set(keyboard_server);
-      cuttlefish::Select(&read_set, nullptr, nullptr, nullptr);
-      {
-        std::lock_guard<std::mutex> lock(socket_mutex_);
-        if (read_set.IsSet(touch_server)) {
-          touch_socket_ = cuttlefish::SharedFD::Accept(*touch_server);
-          LOG(DEBUG) << "connected to touch";
-        }
-        if (read_set.IsSet(keyboard_server)) {
-          keyboard_socket_ = cuttlefish::SharedFD::Accept(*keyboard_server);
-          LOG(DEBUG) << "connected to keyboard";
-        }
-      }
-    }
-  }
-  cuttlefish::SharedFD touch_socket_;
-  cuttlefish::SharedFD keyboard_socket_;
-  std::thread client_connector_;
-  std::mutex socket_mutex_;
-};
-
-VirtualInputs::VirtualInputs() { AddKeyMappings(&keymapping_); }
-
-/**
- * Depending on the host mode (e.g. android, confirmation ui(tee), etc)
- * deliver the inputs to the right input implementation
- * e.g. ConfUI's input or regular socket based input
- */
-class VirtualInputDemux : public VirtualInputs {
- public:
-  VirtualInputDemux(cuttlefish::confui::HostVirtualInput& confui_input)
-      : confui_input_{confui_input} {}
-  virtual ~VirtualInputDemux() = default;
-
-  virtual void GenerateKeyPressEvent(int code, bool down) override;
-  virtual void PressPowerButton(bool down) override;
-  virtual void HandlePointerEvent(bool touch_down, int x, int y) override;
-
- private:
-  SocketVirtualInputs socket_virtual_input_;
-  cuttlefish::confui::HostVirtualInput& confui_input_;
-};
-
-void VirtualInputDemux::GenerateKeyPressEvent(int code, bool down) {
-  // confui input is active only in the confirmation UI
-  // also, socket virtual input should be inactive in the confirmation
-  // UI session
-  if (confui_input_.IsConfUiActive()) {
-    if (code == cuttlefish::xk::Menu) {
-      // release menu button in confirmation UI means for now cancel
-      confui_input_.PressCancelButton(down);
-    }
-    ConfUiLog(DEBUG) << "the key" << code << "ignored."
-                     << "currently confirmation UI handles"
-                     << "menu and power only.";
-    return;
-  }
-  socket_virtual_input_.GenerateKeyPressEvent(code, down);
-}
-
-void VirtualInputDemux::PressPowerButton(bool down) {
-  if (confui_input_.IsConfUiActive()) {
-    confui_input_.PressConfirmButton(down);
-    return;
-  }
-  socket_virtual_input_.PressPowerButton(down);
-}
-
-void VirtualInputDemux::HandlePointerEvent(bool touch_down, int x, int y) {
-  if (confui_input_.IsConfUiActive()) {
-    ConfUiLog(DEBUG) << "currently confirmation UI ignores pointer events at ("
-                     << x << ", " << y << ")";
-    return;
-  }
-  socket_virtual_input_.HandlePointerEvent(touch_down, x, y);
-}
-
-std::shared_ptr<VirtualInputs> VirtualInputs::Get(
-    cuttlefish::confui::HostVirtualInput& confui_input) {
-  return std::make_shared<VirtualInputDemux>(confui_input);
-}
diff --git a/host/frontend/vnc_server/virtual_inputs.h b/host/frontend/vnc_server/virtual_inputs.h
deleted file mode 100644
index f30202e..0000000
--- a/host/frontend/vnc_server/virtual_inputs.h
+++ /dev/null
@@ -1,47 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <map>
-#include <memory>
-#include <mutex>
-
-#include "host/libs/confui/host_virtual_input.h"
-#include "vnc_utils.h"
-
-namespace cuttlefish {
-namespace vnc {
-
-class VirtualInputs {
- public:
-  static std::shared_ptr<VirtualInputs> Get(
-      cuttlefish::confui::HostVirtualInput& confui_input);
-
-  virtual ~VirtualInputs() = default;
-
-  virtual void GenerateKeyPressEvent(int code, bool down) = 0;
-  virtual void PressPowerButton(bool down) = 0;
-  virtual void HandlePointerEvent(bool touch_down, int x, int y) = 0;
-
- protected:
-  VirtualInputs();
-
-  std::map<uint32_t, uint16_t> keymapping_;
-};
-
-}  // namespace vnc
-}  // namespace cuttlefish
diff --git a/host/frontend/vnc_server/vnc_client_connection.cpp b/host/frontend/vnc_server/vnc_client_connection.cpp
deleted file mode 100644
index ce16ec8..0000000
--- a/host/frontend/vnc_server/vnc_client_connection.cpp
+++ /dev/null
@@ -1,685 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "host/frontend/vnc_server/vnc_client_connection.h"
-
-#include <netinet/in.h>
-#include <sys/time.h>
-
-#include <algorithm>
-#include <cmath>
-#include <cstdint>
-#include <cstring>
-#include <memory>
-#include <mutex>
-#include <string>
-#include <thread>
-#include <utility>
-#include <vector>
-
-#include <gflags/gflags.h>
-#include <android-base/logging.h>
-#include "common/libs/utils/tcp_socket.h"
-#include "host/frontend/vnc_server/keysyms.h"
-#include "host/frontend/vnc_server/mocks.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/screen_connector/screen_connector.h"
-
-using cuttlefish::Message;
-using cuttlefish::vnc::Stripe;
-using cuttlefish::vnc::StripePtrVec;
-using cuttlefish::vnc::VncClientConnection;
-
-struct ScreenRegionView {
-  using Pixel = uint32_t;
-  static constexpr int kSwiftShaderPadding = 4;
-  static constexpr int kRedShift = 0;
-  static constexpr int kGreenShift = 8;
-  static constexpr int kBlueShift = 16;
-  static constexpr int kRedBits = 8;
-  static constexpr int kGreenBits = 8;
-  static constexpr int kBlueBits = 8;
-};
-
-DEFINE_bool(debug_client, false, "Turn on detailed logging for the client");
-
-#define DLOG(LEVEL) \
-  if (FLAGS_debug_client) LOG(LEVEL)
-
-namespace {
-class BigEndianChecker {
- public:
-  BigEndianChecker() {
-    uint32_t u = 1;
-    is_big_endian_ = *reinterpret_cast<const char*>(&u) == 0;
-  }
-  bool operator()() const { return is_big_endian_; }
-
- private:
-  bool is_big_endian_{};
-};
-
-const BigEndianChecker ImBigEndian;
-
-constexpr int32_t kDesktopSizeEncoding = -223;
-constexpr int32_t kTightEncoding = 7;
-
-// These are the lengths not counting the first byte. The first byte
-// indicates the message type.
-constexpr size_t kSetPixelFormatLength = 19;
-constexpr size_t kFramebufferUpdateRequestLength = 9;
-constexpr size_t kSetEncodingsLength = 3;  // more bytes follow
-constexpr size_t kKeyEventLength = 7;
-constexpr size_t kPointerEventLength = 5;
-constexpr size_t kClientCutTextLength = 7;  // more bytes follow
-
-std::string HostName() {
-  auto config = cuttlefish::CuttlefishConfig::Get();
-  auto instance = config->ForDefaultInstance();
-  return !config || instance.device_title().empty() ? std::string{"localhost"}
-                                                    : instance.device_title();
-}
-
-std::uint16_t uint16_tAt(const void* p) {
-  std::uint16_t u{};
-  std::memcpy(&u, p, sizeof u);
-  return ntohs(u);
-}
-
-std::uint32_t uint32_tAt(const void* p) {
-  std::uint32_t u{};
-  std::memcpy(&u, p, sizeof u);
-  return ntohl(u);
-}
-
-std::int32_t int32_tAt(const void* p) {
-  std::uint32_t u{};
-  std::memcpy(&u, p, sizeof u);
-  u = ntohl(u);
-  std::int32_t s{};
-  std::memcpy(&s, &u, sizeof s);
-  return s;
-}
-
-std::uint32_t RedVal(std::uint32_t pixel) {
-  return (pixel >> ScreenRegionView::kRedShift) &
-         ((0x1 << ScreenRegionView::kRedBits) - 1);
-}
-
-std::uint32_t BlueVal(std::uint32_t pixel) {
-  return (pixel >> ScreenRegionView::kBlueShift) &
-         ((0x1 << ScreenRegionView::kBlueBits) - 1);
-}
-
-std::uint32_t GreenVal(std::uint32_t pixel) {
-  return (pixel >> ScreenRegionView::kGreenShift) &
-         ((0x1 << ScreenRegionView::kGreenBits) - 1);
-}
-}  // namespace
-namespace cuttlefish {
-namespace vnc {
-bool operator==(const VncClientConnection::FrameBufferUpdateRequest& lhs,
-                const VncClientConnection::FrameBufferUpdateRequest& rhs) {
-  return lhs.x_pos == rhs.x_pos && lhs.y_pos == rhs.y_pos &&
-         lhs.width == rhs.width && lhs.height == rhs.height;
-}
-
-bool operator!=(const VncClientConnection::FrameBufferUpdateRequest& lhs,
-                const VncClientConnection::FrameBufferUpdateRequest& rhs) {
-  return !(lhs == rhs);
-}
-}  // namespace vnc
-}  // namespace cuttlefish
-
-VncClientConnection::VncClientConnection(
-    ClientSocket client, std::shared_ptr<VirtualInputs> virtual_inputs,
-    BlackBoard* bb, bool aggressive)
-    : client_{std::move(client)}, virtual_inputs_{virtual_inputs}, bb_{bb} {
-  frame_buffer_request_handler_tid_ = std::thread(
-      &VncClientConnection::FrameBufferUpdateRequestHandler, this, aggressive);
-}
-
-VncClientConnection::~VncClientConnection() {
-  {
-    std::lock_guard<std::mutex> guard(m_);
-    closed_ = true;
-  }
-  bb_->StopWaiting(this);
-  frame_buffer_request_handler_tid_.join();
-}
-
-void VncClientConnection::StartSession() {
-  LOG(INFO) << "Starting session";
-  SetupProtocol();
-  LOG(INFO) << "Protocol set up";
-  if (client_.closed()) {
-    return;
-  }
-  SetupSecurityType();
-  LOG(INFO) << "Security type set";
-  if (client_.closed()) {
-    return;
-  }
-  GetClientInit();
-  LOG(INFO) << "Gotten client init";
-  if (client_.closed()) {
-    return;
-  }
-  SendServerInit();
-  LOG(INFO) << "Sent server init";
-  if (client_.closed()) {
-    return;
-  }
-  NormalSession();
-  LOG(INFO) << "vnc session terminated";
-}
-
-bool VncClientConnection::closed() {
-  std::lock_guard<std::mutex> guard(m_);
-  return closed_;
-}
-
-void VncClientConnection::SetupProtocol() {
-  static constexpr char kRFBVersion[] = "RFB 003.008\n";
-  static constexpr char kRFBVersionOld[] = "RFB 003.003\n";
-  static constexpr auto kVersionLen = (sizeof kRFBVersion) - 1;
-  client_.SendNoSignal(reinterpret_cast<const std::uint8_t*>(kRFBVersion),
-                       kVersionLen);
-  auto client_protocol = client_.Recv(kVersionLen);
-  if (std::memcmp(&client_protocol[0], kRFBVersion,
-                  std::min(kVersionLen, client_protocol.size())) != 0) {
-    if (!std::memcmp(
-                &client_protocol[0],
-                kRFBVersionOld,
-                std::min(kVersionLen, client_protocol.size()))) {
-        // We'll deal with V3.3 as well.
-        client_is_old_ = true;
-        return;
-    }
-
-    client_protocol.push_back('\0');
-    LOG(ERROR) << "vnc client wants a different protocol: "
-               << reinterpret_cast<const char*>(&client_protocol[0]);
-  }
-}
-
-void VncClientConnection::SetupSecurityType() {
-  if (client_is_old_) {
-    static constexpr std::uint8_t kVNCSecurity[4] = { 0x00, 0x00, 0x00, 0x02 };
-    client_.SendNoSignal(kVNCSecurity);
-
-    static constexpr std::uint8_t kChallenge[16] =
-        { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
-
-    client_.SendNoSignal(kChallenge);
-
-    auto clientResponse = client_.Recv(16);
-    (void)clientResponse;  // Accept any response, we're not interested in actual security.
-
-    static constexpr std::uint8_t kSuccess[4] = { 0x00, 0x00, 0x00, 0x00 };
-    client_.SendNoSignal(kSuccess);
-    return;
-  }
-
-  static constexpr std::uint8_t kNoneSecurity = 0x1;
-  // The first '0x1' indicates the number of items that follow
-  static constexpr std::uint8_t kOnlyNoneSecurity[] = {0x01, kNoneSecurity};
-  client_.SendNoSignal(kOnlyNoneSecurity);
-  auto client_security = client_.Recv(1);
-  if (client_.closed()) {
-    return;
-  }
-  if (client_security.front() != kNoneSecurity) {
-    LOG(ERROR) << "vnc client is asking for security type "
-               << static_cast<int>(client_security.front());
-  }
-  static constexpr std::uint8_t kZero[4] = {};
-  client_.SendNoSignal(kZero);
-}
-
-void VncClientConnection::GetClientInit() {
-  auto client_shared = client_.Recv(1);
-}
-
-void VncClientConnection::SendServerInit() {
-  const std::string server_name = HostName();
-  std::lock_guard<std::mutex> guard(m_);
-  auto server_init = cuttlefish::CreateMessage(
-      static_cast<std::uint16_t>(ScreenWidth()),
-      static_cast<std::uint16_t>(ScreenHeight()), pixel_format_.bits_per_pixel,
-      pixel_format_.depth, pixel_format_.big_endian, pixel_format_.true_color,
-      pixel_format_.red_max, pixel_format_.green_max, pixel_format_.blue_max,
-      pixel_format_.red_shift, pixel_format_.green_shift,
-      pixel_format_.blue_shift, std::uint16_t{},  // padding
-      std::uint8_t{},                             // padding
-      static_cast<std::uint32_t>(server_name.size()), server_name);
-  client_.SendNoSignal(server_init);
-}
-
-Message VncClientConnection::MakeFrameBufferUpdateHeader(
-    std::uint16_t num_stripes) {
-  return cuttlefish::CreateMessage(std::uint8_t{0},  // message-type
-                            std::uint8_t{},   // padding
-                            std::uint16_t{num_stripes});
-}
-
-void VncClientConnection::AppendRawStripeHeader(Message* frame_buffer_update,
-                                                const Stripe& stripe) {
-  static constexpr int32_t kRawEncoding = 0;
-  cuttlefish::AppendToMessage(frame_buffer_update, std::uint16_t{stripe.x},
-                       std::uint16_t{stripe.y}, std::uint16_t{stripe.width},
-                       std::uint16_t{stripe.height}, kRawEncoding);
-}
-
-void VncClientConnection::AppendJpegSize(Message* frame_buffer_update,
-                                         size_t jpeg_size) {
-  constexpr size_t kJpegSizeOneByteMax = 127;
-  constexpr size_t kJpegSizeTwoByteMax = 16383;
-  constexpr size_t kJpegSizeThreeByteMax = 4194303;
-
-  if (jpeg_size <= kJpegSizeOneByteMax) {
-    cuttlefish::AppendToMessage(frame_buffer_update,
-                         static_cast<std::uint8_t>(jpeg_size));
-  } else if (jpeg_size <= kJpegSizeTwoByteMax) {
-    auto sz = static_cast<std::uint32_t>(jpeg_size);
-    cuttlefish::AppendToMessage(frame_buffer_update,
-                         static_cast<std::uint8_t>((sz & 0x7F) | 0x80),
-                         static_cast<std::uint8_t>((sz >> 7) & 0xFF));
-  } else {
-    if (jpeg_size > kJpegSizeThreeByteMax) {
-      LOG(FATAL) << "jpeg size is too big: " << jpeg_size << " must be under "
-                 << kJpegSizeThreeByteMax;
-    }
-    const auto sz = static_cast<std::uint32_t>(jpeg_size);
-    cuttlefish::AppendToMessage(frame_buffer_update,
-                         static_cast<std::uint8_t>((sz & 0x7F) | 0x80),
-                         static_cast<std::uint8_t>(((sz >> 7) & 0x7F) | 0x80),
-                         static_cast<std::uint8_t>((sz >> 14) & 0xFF));
-  }
-}
-
-void VncClientConnection::AppendRawStripe(Message* frame_buffer_update,
-                                          const Stripe& stripe) const {
-  using Pixel = ScreenRegionView::Pixel;
-  auto& fbu = *frame_buffer_update;
-  AppendRawStripeHeader(&fbu, stripe);
-  auto init_size = fbu.size();
-  fbu.insert(fbu.end(), stripe.raw_data.begin(), stripe.raw_data.end());
-  for (size_t i = init_size; i < fbu.size(); i += sizeof(Pixel)) {
-    CHECK_LE(i + sizeof(Pixel), fbu.size());
-    Pixel raw_pixel{};
-    std::memcpy(&raw_pixel, &fbu[i], sizeof raw_pixel);
-    auto red = RedVal(raw_pixel);
-    auto green = GreenVal(raw_pixel);
-    auto blue = BlueVal(raw_pixel);
-    Pixel pixel = Pixel{red} << pixel_format_.red_shift |
-                  Pixel{blue} << pixel_format_.blue_shift |
-                  Pixel{green} << pixel_format_.green_shift;
-
-    if (bool(pixel_format_.big_endian) != ImBigEndian()) {
-      // flip them bits (refactor into function)
-      auto p = reinterpret_cast<char*>(&pixel);
-      std::swap(p[0], p[3]);
-      std::swap(p[1], p[2]);
-    }
-    std::memcpy(&fbu[i], &pixel, sizeof pixel);
-  }
-}
-
-Message VncClientConnection::MakeRawFrameBufferUpdate(
-    const StripePtrVec& stripes) const {
-  auto fbu =
-      MakeFrameBufferUpdateHeader(static_cast<std::uint16_t>(stripes.size()));
-  for (auto& stripe : stripes) {
-    AppendRawStripe(&fbu, *stripe);
-  }
-  return fbu;
-}
-
-void VncClientConnection::AppendJpegStripeHeader(Message* frame_buffer_update,
-                                                 const Stripe& stripe) {
-  static constexpr std::uint8_t kJpegEncoding = 0x90;
-  cuttlefish::AppendToMessage(frame_buffer_update, stripe.x, stripe.y, stripe.width,
-                       stripe.height, kTightEncoding, kJpegEncoding);
-  AppendJpegSize(frame_buffer_update, stripe.jpeg_data.size());
-}
-
-void VncClientConnection::AppendJpegStripe(Message* frame_buffer_update,
-                                           const Stripe& stripe) {
-  AppendJpegStripeHeader(frame_buffer_update, stripe);
-  frame_buffer_update->insert(frame_buffer_update->end(),
-                              stripe.jpeg_data.begin(), stripe.jpeg_data.end());
-}
-
-Message VncClientConnection::MakeJpegFrameBufferUpdate(
-    const StripePtrVec& stripes) {
-  auto fbu =
-      MakeFrameBufferUpdateHeader(static_cast<std::uint16_t>(stripes.size()));
-  for (auto& stripe : stripes) {
-    AppendJpegStripe(&fbu, *stripe);
-  }
-  return fbu;
-}
-
-Message VncClientConnection::MakeFrameBufferUpdate(
-    const StripePtrVec& stripes) {
-  return use_jpeg_compression_ ? MakeJpegFrameBufferUpdate(stripes)
-                               : MakeRawFrameBufferUpdate(stripes);
-}
-
-void VncClientConnection::FrameBufferUpdateRequestHandler(bool aggressive) {
-  BlackBoard::Registerer reg(bb_, this);
-
-  while (!closed()) {
-    auto stripes = bb_->WaitForSenderWork(this);
-    if (closed()) {
-      break;
-    }
-    if (stripes.empty()) {
-      LOG(FATAL) << "Got 0 stripes";
-    }
-    {
-      // lock here so a portrait frame can't be sent after a landscape
-      // DesktopSize update, or vice versa.
-      std::lock_guard<std::mutex> guard(m_);
-      DLOG(INFO) << "Sending update in "
-                 << (current_orientation_ == ScreenOrientation::Portrait
-                         ? "portrait"
-                         : "landscape")
-                 << " mode";
-      client_.SendNoSignal(MakeFrameBufferUpdate(stripes));
-    }
-    if (aggressive) {
-      bb_->FrameBufferUpdateRequestReceived(this);
-    }
-  }
-}
-
-void VncClientConnection::SendDesktopSizeUpdate() {
-  static constexpr int32_t kDesktopSizeEncoding = -223;
-  client_.SendNoSignal(cuttlefish::CreateMessage(
-      std::uint8_t{0},   // message-type,
-      std::uint8_t{},    // padding
-      std::uint16_t{1},  // one pseudo rectangle
-      std::uint16_t{0}, std::uint16_t{0},
-      static_cast<std::uint16_t>(ScreenWidth()),
-      static_cast<std::uint16_t>(ScreenHeight()), kDesktopSizeEncoding));
-}
-
-bool VncClientConnection::IsUrgent(
-    const FrameBufferUpdateRequest& update_request) const {
-  return !update_request.incremental ||
-         update_request != previous_update_request_;
-}
-
-void VncClientConnection::HandleFramebufferUpdateRequest() {
-  auto msg = client_.Recv(kFramebufferUpdateRequestLength);
-  if (msg.size() != kFramebufferUpdateRequestLength) {
-    return;
-  }
-  FrameBufferUpdateRequest fbur{msg[1] == 0, uint16_tAt(&msg[1]),
-                                uint16_tAt(&msg[3]), uint16_tAt(&msg[5]),
-                                uint16_tAt(&msg[7])};
-  if (IsUrgent(fbur)) {
-    bb_->SignalClientNeedsEntireScreen(this);
-  }
-  bb_->FrameBufferUpdateRequestReceived(this);
-  previous_update_request_ = fbur;
-}
-
-void VncClientConnection::HandleSetEncodings() {
-  auto msg = client_.Recv(kSetEncodingsLength);
-  if (msg.size() != kSetEncodingsLength) {
-    return;
-  }
-  auto count = uint16_tAt(&msg[1]);
-  auto encodings = client_.Recv(count * sizeof(int32_t));
-  if (encodings.size() % sizeof(int32_t) != 0) {
-    return;
-  }
-  {
-    std::lock_guard<std::mutex> guard(m_);
-    use_jpeg_compression_ = false;
-  }
-  for (size_t i = 0; i < encodings.size(); i += sizeof(int32_t)) {
-    auto enc = int32_tAt(&encodings[i]);
-    DLOG(INFO) << "client requesting encoding: " << enc;
-    if (enc == kTightEncoding) {
-      // This is a deviation from the spec which says that if a jpeg quality
-      // level is not specified, tight encoding won't use jpeg.
-      std::lock_guard<std::mutex> guard(m_);
-      use_jpeg_compression_ = true;
-    }
-    if (kJpegMinQualityEncoding <= enc && enc <= kJpegMaxQualityEncoding) {
-      DLOG(INFO) << "jpeg compression level: " << enc;
-      bb_->set_jpeg_quality_level(enc);
-    }
-    if (enc == kDesktopSizeEncoding) {
-      supports_desktop_size_encoding_ = true;
-    }
-  }
-}
-
-void VncClientConnection::HandleSetPixelFormat() {
-  std::lock_guard<std::mutex> guard(m_);
-  auto msg = client_.Recv(kSetPixelFormatLength);
-  if (msg.size() != kSetPixelFormatLength) {
-    return;
-  }
-  pixel_format_.bits_per_pixel = msg[3];
-  pixel_format_.depth = msg[4];
-  pixel_format_.big_endian = msg[5];
-  pixel_format_.true_color = msg[7];
-  pixel_format_.red_max = uint16_tAt(&msg[8]);
-  pixel_format_.green_max = uint16_tAt(&msg[10]);
-  pixel_format_.blue_max = uint16_tAt(&msg[12]);
-  pixel_format_.red_shift = msg[13];
-  pixel_format_.green_shift = msg[14];
-  pixel_format_.blue_shift = msg[15];
-}
-
-void VncClientConnection::HandlePointerEvent() {
-  auto msg = client_.Recv(kPointerEventLength);
-  if (msg.size() != kPointerEventLength) {
-    return;
-  }
-  std::uint8_t button_mask = msg[0];
-  auto x_pos = uint16_tAt(&msg[1]);
-  auto y_pos = uint16_tAt(&msg[3]);
-  {
-    std::lock_guard<std::mutex> guard(m_);
-    if (current_orientation_ == ScreenOrientation::Landscape) {
-      std::tie(x_pos, y_pos) =
-          std::make_pair(ScreenConnectorInfo::ScreenWidth(0) - y_pos, x_pos);
-    }
-  }
-  virtual_inputs_->HandlePointerEvent(button_mask, x_pos, y_pos);
-}
-
-void VncClientConnection::UpdateAccelerometer(float /*x*/, float /*y*/,
-                                              float /*z*/) {
-  // TODO(jemoreira): Implement when vsoc sensor hal is updated
-}
-
-VncClientConnection::Coordinates VncClientConnection::CoordinatesForOrientation(
-    ScreenOrientation orientation) const {
-  // Compute the acceleration vector that we need to send to mimic
-  // this change.
-  constexpr float g = 9.81;
-  constexpr float angle = 20.0;
-  const float cos_angle = std::cos(angle / M_PI);
-  const float sin_angle = std::sin(angle / M_PI);
-  const float z = g * sin_angle;
-  switch (orientation) {
-    case ScreenOrientation::Portrait:
-      return {0, g * cos_angle, z};
-    case ScreenOrientation::Landscape:
-      return {g * cos_angle, 0, z};
-  }
-}
-
-int VncClientConnection::ScreenWidth() const {
-  return current_orientation_ == ScreenOrientation::Portrait
-             ? ScreenConnectorInfo::ScreenWidth(0)
-             : ScreenConnectorInfo::ScreenHeight(0);
-}
-
-int VncClientConnection::ScreenHeight() const {
-  return current_orientation_ == ScreenOrientation::Portrait
-             ? ScreenConnectorInfo::ScreenHeight(0)
-             : ScreenConnectorInfo::ScreenWidth(0);
-}
-
-void VncClientConnection::SetScreenOrientation(ScreenOrientation orientation) {
-  std::lock_guard<std::mutex> guard(m_);
-  auto coords = CoordinatesForOrientation(orientation);
-  UpdateAccelerometer(coords.x, coords.y, coords.z);
-  if (supports_desktop_size_encoding_) {
-    auto previous_orientation = current_orientation_;
-    current_orientation_ = orientation;
-    if (current_orientation_ != previous_orientation &&
-        supports_desktop_size_encoding_) {
-      SendDesktopSizeUpdate();
-      bb_->SetOrientation(this, current_orientation_);
-      // TODO not sure if I should be sending a frame update along with this,
-      // or just letting the next FBUR handle it. This seems to me like it's
-      // sending one more frame buffer update than was requested, which is
-      // maybe a violation of the spec?
-    }
-  }
-}
-
-bool VncClientConnection::RotateIfIsRotationCommand(std::uint32_t key) {
-  // Due to different configurations on different platforms we're supporting
-  // a set of options for rotating the screen. These are similar to what
-  // the emulator supports and has supported.
-  // ctrl+left and ctrl+right work on windows and linux
-  // command+left and command+right work on Mac
-  // ctrl+fn+F11 and ctrl+fn+F12 work when chromoting to ubuntu from a Mac
-  if (!control_key_down_ && !meta_key_down_) {
-    return false;
-  }
-  switch (key) {
-    case cuttlefish::xk::Right:
-    case cuttlefish::xk::F12:
-      DLOG(INFO) << "switching to portrait";
-      SetScreenOrientation(ScreenOrientation::Portrait);
-      break;
-    case cuttlefish::xk::Left:
-    case cuttlefish::xk::F11:
-      DLOG(INFO) << "switching to landscape";
-      SetScreenOrientation(ScreenOrientation::Landscape);
-      break;
-    default:
-      return false;
-  }
-  return true;
-}
-
-void VncClientConnection::HandleKeyEvent() {
-  auto msg = client_.Recv(kKeyEventLength);
-  if (msg.size() != kKeyEventLength) {
-    return;
-  }
-
-  auto key = uint32_tAt(&msg[3]);
-  bool key_down = msg[0];
-  switch (key) {
-    case cuttlefish::xk::ControlLeft:
-    case cuttlefish::xk::ControlRight:
-      control_key_down_ = key_down;
-      break;
-    case cuttlefish::xk::MetaLeft:
-    case cuttlefish::xk::MetaRight:
-      meta_key_down_ = key_down;
-      break;
-    case cuttlefish::xk::F5:
-      key = cuttlefish::xk::Menu;
-      break;
-    case cuttlefish::xk::F7:
-      virtual_inputs_->PressPowerButton(key_down);
-      return;
-    default:
-      break;
-  }
-
-  if (RotateIfIsRotationCommand(key)) {
-    return;
-  }
-
-  virtual_inputs_->GenerateKeyPressEvent(key, key_down);
-}
-
-void VncClientConnection::HandleClientCutText() {
-  auto msg = client_.Recv(kClientCutTextLength);
-  if (msg.size() != kClientCutTextLength) {
-    return;
-  }
-  auto len = uint32_tAt(&msg[3]);
-  client_.Recv(len);
-}
-
-void VncClientConnection::NormalSession() {
-  static constexpr std::uint8_t kSetPixelFormatMessage{0};
-  static constexpr std::uint8_t kSetEncodingsMessage{2};
-  static constexpr std::uint8_t kFramebufferUpdateRequestMessage{3};
-  static constexpr std::uint8_t kKeyEventMessage{4};
-  static constexpr std::uint8_t kPointerEventMessage{5};
-  static constexpr std::uint8_t kClientCutTextMessage{6};
-  while (true) {
-    if (client_.closed()) {
-      return;
-    }
-    auto msg = client_.Recv(1);
-    if (client_.closed()) {
-      return;
-    }
-    auto msg_type = msg.front();
-    DLOG(INFO) << "Received message type " << msg_type;
-
-    switch (msg_type) {
-      case kSetPixelFormatMessage:
-        HandleSetPixelFormat();
-        break;
-
-      case kSetEncodingsMessage:
-        HandleSetEncodings();
-        break;
-
-      case kFramebufferUpdateRequestMessage:
-        HandleFramebufferUpdateRequest();
-        break;
-
-      case kKeyEventMessage:
-        HandleKeyEvent();
-        break;
-
-      case kPointerEventMessage:
-        HandlePointerEvent();
-        break;
-
-      case kClientCutTextMessage:
-        HandleClientCutText();
-        break;
-
-      default:
-        LOG(WARNING) << "message type not handled: "
-                     << static_cast<int>(msg_type);
-        break;
-    }
-  }
-}
diff --git a/host/frontend/vnc_server/vnc_client_connection.h b/host/frontend/vnc_server/vnc_client_connection.h
deleted file mode 100644
index b26cf86..0000000
--- a/host/frontend/vnc_server/vnc_client_connection.h
+++ /dev/null
@@ -1,173 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "common/libs/concurrency/thread_annotations.h"
-#include "common/libs/fs/shared_fd.h"
-
-#include <cstdint>
-#include <memory>
-#include <mutex>
-#include <thread>
-#include <vector>
-
-#include "common/libs/utils/tcp_socket.h"
-#include "host/frontend/vnc_server/blackboard.h"
-#include "host/frontend/vnc_server/virtual_inputs.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-
-namespace cuttlefish {
-namespace vnc {
-
-class VncClientConnection {
- public:
-  VncClientConnection(ClientSocket client,
-                      std::shared_ptr<VirtualInputs> virtual_inputs,
-                      BlackBoard* bb, bool aggressive);
-  VncClientConnection(const VncClientConnection&) = delete;
-  VncClientConnection& operator=(const VncClientConnection&) = delete;
-  ~VncClientConnection();
-
-  void StartSession();
-
- private:
-  struct PixelFormat {
-    std::uint8_t bits_per_pixel;
-    std::uint8_t depth;
-    std::uint8_t big_endian;
-    std::uint8_t true_color;
-    std::uint16_t red_max;
-    std::uint16_t green_max;
-    std::uint16_t blue_max;
-    std::uint8_t red_shift;
-    std::uint8_t green_shift;
-    std::uint8_t blue_shift;
-  };
-
-  struct FrameBufferUpdateRequest {
-    bool incremental;
-    std::uint16_t x_pos;
-    std::uint16_t y_pos;
-    std::uint16_t width;
-    std::uint16_t height;
-  };
-
-  friend bool operator==(const FrameBufferUpdateRequest&,
-                         const FrameBufferUpdateRequest&);
-  friend bool operator!=(const FrameBufferUpdateRequest&,
-                         const FrameBufferUpdateRequest&);
-
-  bool closed();
-  void SetupProtocol();
-  void SetupSecurityType();
-
-  void GetClientInit();
-
-  void SendServerInit() EXCLUDES(m_);
-  static Message MakeFrameBufferUpdateHeader(std::uint16_t num_stripes);
-
-  static void AppendRawStripeHeader(Message* frame_buffer_update,
-                                    const Stripe& stripe);
-  void AppendRawStripe(Message* frame_buffer_update, const Stripe& stripe) const
-      REQUIRES(m_);
-  Message MakeRawFrameBufferUpdate(const StripePtrVec& stripes) const
-      REQUIRES(m_);
-
-  static void AppendJpegSize(Message* frame_buffer_update, size_t jpeg_size);
-  static void AppendJpegStripeHeader(Message* frame_buffer_update,
-                                     const Stripe& stripe);
-  static void AppendJpegStripe(Message* frame_buffer_update,
-                               const Stripe& stripe);
-  static Message MakeJpegFrameBufferUpdate(const StripePtrVec& stripes);
-
-  Message MakeFrameBufferUpdate(const StripePtrVec& frame) REQUIRES(m_);
-
-  void FrameBufferUpdateRequestHandler(bool aggressive) EXCLUDES(m_);
-
-  void SendDesktopSizeUpdate() REQUIRES(m_);
-
-  bool IsUrgent(const FrameBufferUpdateRequest& update_request) const;
-  static StripeSeqNumber MostRecentStripeSeqNumber(const StripePtrVec& stripes);
-
-  void HandleFramebufferUpdateRequest() EXCLUDES(m_);
-
-  void HandleSetEncodings();
-
-  void HandleSetPixelFormat();
-
-  void HandlePointerEvent() EXCLUDES(m_);
-
-  void UpdateAccelerometer(float x, float y, float z);
-
-  struct Coordinates {
-    float x;
-    float y;
-    float z;
-  };
-
-  Coordinates CoordinatesForOrientation(ScreenOrientation orientation) const;
-
-  int ScreenWidth() const REQUIRES(m_);
-
-  int ScreenHeight() const REQUIRES(m_);
-
-  void SetScreenOrientation(ScreenOrientation orientation) EXCLUDES(m_);
-
-  // Returns true if key is special and the screen was rotated.
-  bool RotateIfIsRotationCommand(std::uint32_t key);
-
-  void HandleKeyEvent();
-
-  void HandleClientCutText();
-
-  void NormalSession();
-
-  mutable std::mutex m_;
-  ClientSocket client_;
-  bool control_key_down_ = false;
-  bool meta_key_down_ = false;
-  std::shared_ptr<VirtualInputs> virtual_inputs_{};
-
-  FrameBufferUpdateRequest previous_update_request_{};
-  BlackBoard* bb_;
-  bool use_jpeg_compression_ GUARDED_BY(m_) = false;
-
-  std::thread frame_buffer_request_handler_tid_;
-  bool closed_ GUARDED_BY(m_){};
-
-  PixelFormat pixel_format_ GUARDED_BY(m_) = {
-      std::uint8_t{32},  // bits per pixel
-      std::uint8_t{24},   // depth
-      std::uint8_t{0},    // big_endian
-      std::uint8_t{1},    // true_color
-      std::uint16_t{0xff},   // red_max, (maxes not used when true color flag is 0)
-      std::uint16_t{0xff},   // green_max
-      std::uint16_t{0xff},   // blue_max
-      std::uint8_t{0},  // red_shift (shifts not used when true color flag is 0)
-      std::uint8_t{8},  // green_shift
-      std::uint8_t{16},  // blue_shift
-  };
-
-  bool supports_desktop_size_encoding_ = false;
-  ScreenOrientation current_orientation_ GUARDED_BY(m_) =
-      ScreenOrientation::Portrait;
-
-  bool client_is_old_ = false;
-};
-
-}  // namespace vnc
-}  // namespace cuttlefish
diff --git a/host/frontend/vnc_server/vnc_server.cpp b/host/frontend/vnc_server/vnc_server.cpp
deleted file mode 100644
index eff0d57..0000000
--- a/host/frontend/vnc_server/vnc_server.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "host/frontend/vnc_server/vnc_server.h"
-
-#include <memory>
-
-#include <android-base/logging.h>
-#include "common/libs/utils/tcp_socket.h"
-#include "host/frontend/vnc_server/blackboard.h"
-#include "host/frontend/vnc_server/frame_buffer_watcher.h"
-#include "host/frontend/vnc_server/jpeg_compressor.h"
-#include "host/frontend/vnc_server/virtual_inputs.h"
-#include "host/frontend/vnc_server/vnc_client_connection.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-
-using cuttlefish::vnc::VncServer;
-
-VncServer::VncServer(int port, bool aggressive,
-                     cuttlefish::vnc::ScreenConnector& screen_connector,
-                     cuttlefish::confui::HostVirtualInput& confui_input)
-    : server_(port),
-      virtual_inputs_(VirtualInputs::Get(confui_input)),
-      frame_buffer_watcher_{&bb_, screen_connector},
-      aggressive_{aggressive} {}
-
-void VncServer::MainLoop() {
-  while (true) {
-    LOG(DEBUG) << "Awaiting connections";
-    auto connection = server_.Accept();
-    LOG(DEBUG) << "Accepted a client connection";
-    StartClient(std::move(connection));
-  }
-}
-
-void VncServer::StartClient(ClientSocket sock) {
-  std::thread t(&VncServer::StartClientThread, this, std::move(sock));
-  t.detach();
-}
-
-void VncServer::StartClientThread(ClientSocket sock) {
-  // NOTE if VncServer is expected to be destroyed, we have a problem here.
-  // All of the client threads will be pointing to the VncServer's
-  // data members. In the current setup, if the VncServer is destroyed with
-  // clients still running, the clients will all be left with dangling
-  // pointers.
-  frame_buffer_watcher_.IncClientCount();
-  VncClientConnection client(std::move(sock), virtual_inputs_, &bb_,
-                             aggressive_);
-  client.StartSession();
-  frame_buffer_watcher_.DecClientCount();
-}
diff --git a/host/frontend/vnc_server/vnc_server.h b/host/frontend/vnc_server/vnc_server.h
deleted file mode 100644
index 2751fd1..0000000
--- a/host/frontend/vnc_server/vnc_server.h
+++ /dev/null
@@ -1,64 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <memory>
-#include <string>
-#include <thread>
-#include <utility>
-
-#include "common/libs/utils/tcp_socket.h"
-#include "host/frontend/vnc_server/blackboard.h"
-#include "host/frontend/vnc_server/frame_buffer_watcher.h"
-#include "host/frontend/vnc_server/jpeg_compressor.h"
-#include "host/frontend/vnc_server/virtual_inputs.h"
-#include "host/frontend/vnc_server/vnc_client_connection.h"
-#include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/confui/host_mode_ctrl.h"
-#include "host/libs/confui/host_virtual_input.h"
-#include "host/libs/screen_connector/screen_connector.h"
-
-namespace cuttlefish {
-namespace vnc {
-
-class VncServer {
- public:
-  explicit VncServer(int port, bool aggressive,
-                     ScreenConnector& screen_connector,
-                     cuttlefish::confui::HostVirtualInput& confui_input);
-
-  VncServer(const VncServer&) = delete;
-  VncServer& operator=(const VncServer&) = delete;
-
-  [[noreturn]] void MainLoop();
-
- private:
-  void StartClient(ClientSocket sock);
-
-  void StartClientThread(ClientSocket sock);
-
-  ServerSocket server_;
-
-  std::shared_ptr<VirtualInputs> virtual_inputs_;
-  BlackBoard bb_;
-
-  FrameBufferWatcher frame_buffer_watcher_;
-  bool aggressive_{};
-};
-
-}  // namespace vnc
-}  // namespace cuttlefish
diff --git a/host/frontend/vnc_server/vnc_utils.h b/host/frontend/vnc_server/vnc_utils.h
deleted file mode 100644
index 7ec19f7..0000000
--- a/host/frontend/vnc_server/vnc_utils.h
+++ /dev/null
@@ -1,90 +0,0 @@
-#pragma once
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <array>
-#include <cstdint>
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "common/libs/utils/size_utils.h"
-#include "common/libs/utils/tcp_socket.h"
-#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/screen_connector/screen_connector.h"
-
-namespace cuttlefish {
-namespace vnc {
-
-// TODO(haining) when the hwcomposer gives a sequence number type, use that
-// instead. It might just work to replace this class with a type alias
-// using StripeSeqNumber = whatever_the_hwcomposer_uses;
-class StripeSeqNumber {
- public:
-  StripeSeqNumber() = default;
-  explicit StripeSeqNumber(std::uint64_t t) : t_{t} {}
-  bool operator<(const StripeSeqNumber& other) const { return t_ < other.t_; }
-
-  bool operator<=(const StripeSeqNumber& other) const { return t_ <= other.t_; }
-
- private:
-  std::uint64_t t_{};
-};
-
-constexpr int32_t kJpegMaxQualityEncoding = -23;
-constexpr int32_t kJpegMinQualityEncoding = -32;
-
-enum class ScreenOrientation { Portrait, Landscape };
-constexpr int kNumOrientations = 2;
-
-struct Stripe {
-  int index = -1;
-  std::uint64_t frame_id{};
-  std::uint16_t x{};
-  std::uint16_t y{};
-  std::uint16_t width{};
-  std::uint16_t stride{};
-  std::uint16_t height{};
-  Message raw_data{};
-  Message jpeg_data{};
-  StripeSeqNumber seq_number{};
-  ScreenOrientation orientation{};
-};
-
-/**
- * ScreenConnectorImpl will generate this, and enqueue
- *
- * It's basically a (processed) frame, so it:
- *   must be efficiently std::move-able
- * Also, for the sake of algorithm simplicity:
- *   must be default-constructable & assignable
- *
- */
-struct VncScProcessedFrame : public ScreenConnectorFrameInfo {
-  Message raw_screen_;
-  std::deque<Stripe> stripes_;
-  std::unique_ptr<VncScProcessedFrame> Clone() {
-    VncScProcessedFrame* cloned_frame = new VncScProcessedFrame();
-    cloned_frame->raw_screen_ = raw_screen_;
-    cloned_frame->stripes_ = stripes_;
-    return std::unique_ptr<VncScProcessedFrame>(cloned_frame);
-  }
-};
-using ScreenConnector = cuttlefish::ScreenConnector<VncScProcessedFrame>;
-
-}  // namespace vnc
-}  // namespace cuttlefish
diff --git a/host/frontend/webrtc/Android.bp b/host/frontend/webrtc/Android.bp
index 42f7fa4..d4384c5 100644
--- a/host/frontend/webrtc/Android.bp
+++ b/host/frontend/webrtc/Android.bp
@@ -31,7 +31,7 @@
         "lib/utils.cpp",
         "lib/video_track_source_impl.cpp",
         "lib/vp8only_encoder_factory.cpp",
-        "lib/ws_connection.cpp",
+        "lib/server_connection.cpp",
     ],
     cflags: [
         // libwebrtc headers need this
@@ -62,10 +62,12 @@
         "libwebrtc_absl_types",
     ],
     shared_libs: [
-        "libssl",
         "libbase",
+        "libcn-cbor",
         "libcuttlefish_fs",
+        "libfruit",
         "libjsoncpp",
+        "libssl",
         "libwebm_mkvmuxer",
     ],
     defaults: ["cuttlefish_buildhost_only"],
@@ -77,6 +79,7 @@
         "adb_handler.cpp",
         "audio_handler.cpp",
         "bluetooth_handler.cpp",
+        "client_server.cpp",
         "connection_observer.cpp",
         "cvd_video_frame_buffer.cpp",
         "display_handler.cpp",
@@ -102,13 +105,15 @@
         "libwebrtc_absl_types",
         "libaom",
         "libcap",
+        "libcn-cbor",
         "libcuttlefish_audio_connector",
+        "libcuttlefish_confui",
+        "libcuttlefish_confui_host",
         "libcuttlefish_host_config",
+        "libcuttlefish_security",
         "libcuttlefish_screen_connector",
         "libcuttlefish_utils",
         "libcuttlefish_wayland_server",
-        "libcuttlefish_confui",
-        "libcuttlefish_confui_host",
         "libft2.nodep",
         "libteeui",
         "libteeui_localization",
@@ -128,11 +133,14 @@
         "libyuv",
     ],
     shared_libs: [
+        "libext2_blkid",
+        "android.hardware.keymaster@4.0",
         "libbase",
         "libcrypto",
         "libcuttlefish_fs",
         "libcuttlefish_kernel_log_monitor_utils",
         "libjsoncpp",
+        "libfruit",
         "libopus",
         "libssl",
         "libvpx",
@@ -141,3 +149,59 @@
     ],
     defaults: ["cuttlefish_buildhost_only"],
 }
+
+prebuilt_usr_share_host {
+    name: "webrtc_client.html",
+    src: "client/client.html",
+    filename: "client.html",
+    sub_dir: "webrtc/assets",
+}
+
+prebuilt_usr_share_host {
+    name: "webrtc_style.css",
+    src: "client/style.css",
+    filename: "style.css",
+    sub_dir: "webrtc/assets",
+}
+
+prebuilt_usr_share_host {
+    name: "webrtc_controls.css",
+    src: "client/controls.css",
+    filename: "controls.css",
+    sub_dir: "webrtc/assets",
+}
+
+prebuilt_usr_share_host {
+    name: "webrtc_adb.js",
+    src: "client/js/adb.js",
+    filename: "adb.js",
+    sub_dir: "webrtc/assets/js",
+}
+
+prebuilt_usr_share_host {
+    name: "webrtc_cf.js",
+    src: "client/js/cf_webrtc.js",
+    filename: "cf_webrtc.js",
+    sub_dir: "webrtc/assets/js",
+}
+
+prebuilt_usr_share_host {
+    name: "webrtc_app.js",
+    src: "client/js/app.js",
+    filename: "app.js",
+    sub_dir: "webrtc/assets/js",
+}
+
+prebuilt_usr_share_host {
+    name: "webrtc_controls.js",
+    src: "client/js/controls.js",
+    filename: "controls.js",
+    sub_dir: "webrtc/assets/js",
+}
+
+prebuilt_usr_share_host {
+    name: "webrtc_rootcanal.js",
+    src: "client/js/rootcanal.js",
+    filename: "rootcanal.js",
+    sub_dir: "webrtc/assets/js",
+}
diff --git a/host/frontend/webrtc/adb_handler.cpp b/host/frontend/webrtc/adb_handler.cpp
index c580f65..7ff55fb 100644
--- a/host/frontend/webrtc/adb_handler.cpp
+++ b/host/frontend/webrtc/adb_handler.cpp
@@ -44,7 +44,11 @@
   }
 
   auto local_client = SharedFD::SocketLocalClient(port, SOCK_STREAM);
-  CHECK(local_client->IsOpen()) << "Failed to connect to adb socket: " << local_client->StrError();
+  if (!local_client->IsOpen()) {
+    LOG(WARNING) << "Failed to connect to ADB server socket (non-Android guest?) Using /dev/null workaround."
+                 << local_client->StrError();
+    return SharedFD::Open("/dev/null", O_RDWR);
+  }
   return local_client;
 }
 
diff --git a/host/frontend/webrtc/audio_handler.cpp b/host/frontend/webrtc/audio_handler.cpp
index 1cd8938..52a2294 100644
--- a/host/frontend/webrtc/audio_handler.cpp
+++ b/host/frontend/webrtc/audio_handler.cpp
@@ -484,18 +484,69 @@
       buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len());
       return;
     }
-    auto bytes_per_sample = stream_desc.bits_per_sample / 8;
-    auto samples_per_channel =
-        buffer.len() / stream_desc.channels / bytes_per_sample;
+    const auto bytes_per_sample = stream_desc.bits_per_sample / 8;
+    const auto samples_per_channel = stream_desc.sample_rate / 100;
+    const auto bytes_per_request =
+        samples_per_channel * bytes_per_sample * stream_desc.channels;
     bool muted = false;
-    auto res = audio_source_->GetMoreAudioData(
-        const_cast<uint8_t*>(buffer.get()), bytes_per_sample,
-        samples_per_channel, stream_desc.channels, stream_desc.sample_rate,
-        muted);
-    if (res < 0) {
-      // This is likely a recoverable error, log the error but don't let the VMM
-      // know about it so that it doesn't crash.
-      LOG(ERROR) << "Failed to receive audio data from client";
+    size_t bytes_read = 0;
+    auto& holding_buffer = stream_descs_[stream_id].buffer;
+    auto rx_buffer = const_cast<uint8_t*>(buffer.get());
+    if (!holding_buffer.empty()) {
+      // Consume any bytes remaining from previous requests
+      bytes_read += holding_buffer.Take(rx_buffer + bytes_read,
+                                        buffer.len() - bytes_read);
+    }
+    while (buffer.len() - bytes_read >= bytes_per_request) {
+      // Skip the holding buffer in as many reads as possible to avoid the extra
+      // copies
+      auto write_pos = rx_buffer + bytes_read;
+      auto res = audio_source_->GetMoreAudioData(
+          write_pos, bytes_per_sample, samples_per_channel,
+          stream_desc.channels, stream_desc.sample_rate, muted);
+      if (res < 0) {
+        // This is likely a recoverable error, log the error but don't let the
+        // VMM know about it so that it doesn't crash.
+        LOG(ERROR) << "Failed to receive audio data from client";
+        break;
+      }
+      if (muted) {
+        // The source is muted, just fill the buffer with zeros and return
+        memset(rx_buffer + bytes_read, 0, buffer.len() - bytes_read);
+        bytes_read = buffer.len();
+        break;
+      }
+      auto bytes_received = res * bytes_per_sample * stream_desc.channels;
+      bytes_read += bytes_received;
+    }
+    if (bytes_read < buffer.len()) {
+      // There is some buffer left to fill, but it's less than 10ms, read into
+      // holding buffer to ensure the remainder is kept around for future reads
+      auto write_pos = holding_buffer.data();
+      // Holding buffer is the exact size we need to read into and is emptied
+      // before we try to read into it.
+      CHECK(holding_buffer.freeCapacity() >= bytes_per_request)
+          << "Buffer too small for receiving audio";
+      auto res = audio_source_->GetMoreAudioData(
+          write_pos, bytes_per_sample, samples_per_channel,
+          stream_desc.channels, stream_desc.sample_rate, muted);
+      if (res < 0) {
+        // This is likely a recoverable error, log the error but don't let the
+        // VMM know about it so that it doesn't crash.
+        LOG(ERROR) << "Failed to receive audio data from client";
+      } else if (muted) {
+        // The source is muted, just fill the buffer with zeros and return
+        memset(rx_buffer + bytes_read, 0, buffer.len() - bytes_read);
+        bytes_read = buffer.len();
+      } else {
+        auto bytes_received = res * bytes_per_sample * stream_desc.channels;
+        holding_buffer.count += bytes_received;
+        bytes_read += holding_buffer.Take(rx_buffer + bytes_read,
+                                          buffer.len() - bytes_read);
+        // If the entire buffer is not full by now there is a bug above
+        // somewhere
+        CHECK(bytes_read == buffer.len()) << "Failed to read entire buffer";
+      }
     }
   }
   buffer.SendStatus(AudioStatus::VIRTIO_SND_S_OK, 0, buffer.len());
@@ -514,12 +565,24 @@
   return added_len;
 }
 
+size_t AudioHandler::HoldingBuffer::Take(uint8_t* dst, size_t len) {
+  auto n = std::min(len, count);
+  std::copy(buffer.begin(), buffer.begin() + n, dst);
+  std::copy(buffer.begin() + n, buffer.begin() + count, buffer.begin());
+  count -= n;
+  return n;
+}
+
 bool AudioHandler::HoldingBuffer::empty() const { return count == 0; }
 
 bool AudioHandler::HoldingBuffer::full() const {
   return count == buffer.size();
 }
 
+size_t AudioHandler::HoldingBuffer::freeCapacity() const {
+  return buffer.size() - count;
+}
+
 uint8_t* AudioHandler::HoldingBuffer::data() { return buffer.data(); }
 
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc/audio_handler.h b/host/frontend/webrtc/audio_handler.h
index 4da645c..fc4734a 100644
--- a/host/frontend/webrtc/audio_handler.h
+++ b/host/frontend/webrtc/audio_handler.h
@@ -35,9 +35,12 @@
 
     void Reset(size_t size);
     size_t Add(const volatile uint8_t* data, size_t max_len);
+    size_t Take(uint8_t* dst, size_t len);
     bool empty() const;
     bool full() const;
+    size_t freeCapacity() const;
     uint8_t* data();
+    uint8_t* end();
   };
   struct StreamDesc {
     std::mutex mtx;
diff --git a/host/frontend/webrtc/client/client.html b/host/frontend/webrtc/client/client.html
new file mode 100644
index 0000000..3ffb97b
--- /dev/null
+++ b/host/frontend/webrtc/client/client.html
@@ -0,0 +1,158 @@
+<?--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<html>
+    <head>
+        <link rel="stylesheet" type="text/css" href="style.css" >
+        <link rel="stylesheet" type="text/css" href="controls.css" >
+        <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined">
+        <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
+        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
+    </head>
+
+    <body>
+      <div id="loader"></div>
+      <div id="error-message-div">
+        <h3 id="error-message" class="hidden">
+          <span class="material-icons close-btn">close</span>
+        </h3>
+      </div>
+      <section id="device-connection">
+        <div id='header'>
+          <div id='app-controls'>
+            <div id="keyboard-capture-control" title="Capture Keyboard"></div>
+            <div id="mic-capture-control" title="Capture Microphone"></div>
+            <div id="camera-control" title="Capture Camera"></div>
+            <div id="audio-playback-control" title="Play audio">
+              <audio id="device-audio"></audio>
+            </div>
+            <div id="record-video-control" title="Capture Display as Webm"></div>
+          </div>
+          <div id='status-div'>
+            <h3 id='status-message' class='connecting'>Connecting to device</h3>
+          </div>
+        </div>
+        <div id='controls-and-displays'>
+          <div id='control-panel-default-buttons' class='control-panel-column'>
+            <button id='device-details-button' title='Device Details' class='material-icons'>
+              settings
+            </button>
+            <button id='bluetooth-modal-button' title='Bluetooth console' class='material-icons'>
+              settings_bluetooth
+            </button>
+          </div>
+          <div id='control-panel-custom-buttons' class='control-panel-column'></div>
+          <div id='device-displays'>
+          </div>
+        </div>
+      </section>
+      <div id='device-details-modal' class='modal'>
+        <div id='device-details-modal-header' class='modal-header'>
+          <h2>Device Details</h2>
+          <button id='device-details-close' title='Close' class='material-icons modal-close'>close</button>
+        </div>
+        <hr>
+        <h3>Hardware Configuration</h3>
+        <span id='device-details-hardware'>unknown</span>
+      </div>
+
+      <div id='bluetooth-modal' class='modal-wrapper'>
+        <div id='bluetooth-prompt' class='modal'>
+          <div id='bluetooth-prompt-header' class='modal-header'>
+            <h2>Bluetooth</h2>
+            <button id='bluetooth-prompt-close' title='Close' class='material-icons modal-close'>close</button>
+          </div>
+          <div>
+            <div id='bluetooth-prompt-text' class='bluetooth-text'>
+            We have enabled a BT Wizard to simplify adding a<br>bluetooth device.<br>
+            Alternatively, you can enter the BT Console if you<br>want to exercise full control.</div><br>
+            <div class='bluetooth-button'>
+              <button id='bluetooth-prompt-wizard' title='Start Wizard' class='modal-button-highlight'>Start Wizard</button>
+              <button id='bluetooth-prompt-list' title='Device List' class='modal-button'>Device List</button>
+              <button id='bluetooth-prompt-console' title='BT Console' class='modal-button'>BT Console</button>
+            </div>
+          </div>
+        </div>
+        <div id='bluetooth-wizard' class='modal'>
+          <div id='bluetooth-wizard-modal-header' class='modal-header'>
+            <h2>BT Wizard</h2>
+            <button id='bluetooth-wizard-close' title='Close' class='material-icons modal-close'>close</button>
+          </div>
+          <div>
+            <div class='bluetooth-text-field'><input type="text" id='bluetooth-wizard-name' placeholder="Device Name"></input></div>
+            <div class='bluetooth-drop-down'>
+              <select id='bluetooth-wizard-type' validate-mac="true" required>
+                <option value="beacon">Beacon</option>
+                <option value="beacon_swarm">Beacon Swarm</option>
+                <!-- Disabled because they were "started but never finished" (according to mylesgw@)
+                <option value="car_kit">Car Kit</option>
+                <option value="classic">Classic</option> -->
+                <option value="keyboard">Keyboard</option>
+                <option value="remote_loopback">Remote Loopback</option>
+                <option value="scripted_beacon">Scripted Beacon</option>
+                <!-- Disabled because it will never show up in the UI
+                <option value="sniffer">Sniffer</option> -->
+              </select>
+            </div>
+            <div class='bluetooth-text-field'><input type="text" id='bluetooth-wizard-mac' placeholder="Device MAC" validate-mac="true" required></input><span></span></div>
+            <div class='bluetooth-button'>
+              <button id='bluetooth-wizard-device' title='Add Device' class='modal-button-highlight' disabled>Add Device</button>
+              <button id='bluetooth-wizard-cancel' title='Cancel' class='modal-button'>Cancel</button>
+            </div>
+          </div>
+        </div>
+        <div id='bluetooth-wizard-confirm' class='modal'>
+          <div id='bluetooth-wizard-confirm-header' class='modal-header'>
+            <h2>BT Wizard</h2>
+            <button id='bluetooth-wizard-confirm-close' title='Close' class='material-icons modal-close'>close</button>
+          </div>
+          <div id='bluetooth-wizard-text' class='bluetooth-text'>Device added. See device details below.</div><br>
+          <div class='bluetooth-text'>
+            <p>Name: <b>GKeyboard</b></p>
+            <p>Type: <b>Keyboard</b></p>
+            <p>MAC Addr: <b>be:ac:01:55:00:03</b></p>
+          </div>
+          <div class='bluetooth-button'><button id='bluetooth-wizard-another' title='Add Another' class='modal-button-highlight'>Add Another</button></div>
+        </div>
+        <div id='bluetooth-list' class='modal'>
+          <div id='bluetooth-list-header' class='modal-header'>
+            <h2>Device List</h2>
+            <button id='bluetooth-list-close' title='Close' class='material-icons modal-close'>close</button>
+          </div>
+          <div class='bluetooth-text'>
+            <div><button title="Delete" data-device-id="delete" class="bluetooth-list-trash material-icons">delete</button>GKeyboard | Keyboard | be:ac:01:55:00:03</div>
+            <div><button title="Delete" data-device-id="delete" class="bluetooth-list-trash material-icons">delete</button>GHeadphones | Audio | dc:fa:32:00:55:02</div>
+          </div>
+        </div>
+        <div id='bluetooth-console' class='modal'>
+          <div id='bluetooth-console-modal-header' class='modal-header'>
+            <h2>BT Console</h2>
+            <button id='bluetooth-console-close' title='Close' class='material-icons modal-close'>close</button>
+          </div>
+          <div>
+            <div colspan='2'><textarea id='bluetooth-console-view' readonly rows='10' cols='60'></textarea></div>
+            <div width='1'><p id='bluetooth-console-cmd-label'>Command:</p></div>
+            <div width='100'><input id='bluetooth-console-input' type='text'></input></div>
+          </div>
+        </div>
+      </div>
+       <script src="js/adb.js"></script>
+       <script src="js/rootcanal.js"></script>
+       <script src="js/cf_webrtc.js" type="module"></script>
+       <script src="js/controls.js"></script>
+       <script src="js/app.js"></script>
+    </body>
+</html>
diff --git a/host/frontend/webrtc_operator/assets/controls.css b/host/frontend/webrtc/client/controls.css
similarity index 100%
rename from host/frontend/webrtc_operator/assets/controls.css
rename to host/frontend/webrtc/client/controls.css
diff --git a/host/frontend/webrtc/client/js/adb.js b/host/frontend/webrtc/client/js/adb.js
new file mode 100644
index 0000000..b011114
--- /dev/null
+++ b/host/frontend/webrtc/client/js/adb.js
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+let adb_ws;
+
+let utf8Encoder = new TextEncoder();
+let utf8Decoder = new TextDecoder();
+
+const A_CNXN = 0x4e584e43;
+const A_OPEN = 0x4e45504f;
+const A_WRTE = 0x45545257;
+const A_OKAY = 0x59414b4f;
+
+const kLocalChannelId = 666;
+
+let array = new Uint8Array();
+
+function setU32LE(array, offset, x) {
+  array[offset] = x & 0xff;
+  array[offset + 1] = (x >> 8) & 0xff;
+  array[offset + 2] = (x >> 16) & 0xff;
+  array[offset + 3] = x >> 24;
+}
+
+function getU32LE(array, offset) {
+  let x = array[offset] | (array[offset + 1] << 8) | (array[offset + 2] << 16) |
+      (array[offset + 3] << 24);
+
+  return x >>> 0;  // convert signed to unsigned if necessary.
+}
+
+function computeChecksum(array) {
+  let sum = 0;
+  let i;
+  for (i = 0; i < array.length; ++i) {
+    sum = ((sum + array[i]) & 0xffffffff) >>> 0;
+  }
+
+  return sum;
+}
+
+function createAdbMessage(command, arg0, arg1, payload) {
+  let arrayBuffer = new ArrayBuffer(24 + payload.length);
+  let array = new Uint8Array(arrayBuffer);
+  setU32LE(array, 0, command);
+  setU32LE(array, 4, arg0);
+  setU32LE(array, 8, arg1);
+  setU32LE(array, 12, payload.length);
+  setU32LE(array, 16, computeChecksum(payload));
+  setU32LE(array, 20, command ^ 0xffffffff);
+  array.set(payload, 24);
+
+  return arrayBuffer;
+}
+
+function adbOpenConnection() {
+  let systemIdentity = utf8Encoder.encode('Cray_II:1234:whatever');
+
+  let arrayBuffer =
+      createAdbMessage(A_CNXN, 0x1000000, 256 * 1024, systemIdentity);
+
+  adb_ws.send(arrayBuffer);
+}
+
+function adbShell(command) {
+  let destination = utf8Encoder.encode('shell:' + command);
+
+  let arrayBuffer = createAdbMessage(A_OPEN, kLocalChannelId, 0, destination);
+  adb_ws.send(arrayBuffer);
+  awaitConnection();
+}
+
+function adbSendOkay(remoteId) {
+  let payload = new Uint8Array(0);
+
+  let arrayBuffer =
+      createAdbMessage(A_OKAY, kLocalChannelId, remoteId, payload);
+
+  adb_ws.send(arrayBuffer);
+}
+
+function JoinArrays(arr1, arr2) {
+  let arr = new Uint8Array(arr1.length + arr2.length);
+  arr.set(arr1, 0);
+  arr.set(arr2, arr1.length);
+  return arr;
+}
+
+// Simple lifecycle management that executes callbacks based on connection
+// state.
+//
+// Any attempt to initiate a command (e.g. creating a connection, sending a
+// message) (re)starts a timer. Any response back from any command stops that
+// timer.
+const timeoutMs = 3000;
+let connectedCb;
+let disconnectedCb;
+let disconnectedTimeout;
+function awaitConnection() {
+  clearTimeout(disconnectedTimeout);
+  if (disconnectedCb) {
+    disconnectedTimeout = setTimeout(disconnectedCb, timeoutMs);
+  }
+}
+function connected() {
+  if (disconnectedTimeout) {
+    clearTimeout(disconnectedTimeout);
+  }
+  if (connectedCb) {
+    connectedCb();
+  }
+}
+
+function adbOnMessage(arrayBuffer) {
+  // console.debug("adb_ws: onmessage (" + arrayBuffer.byteLength + " bytes)");
+  array = JoinArrays(array, new Uint8Array(arrayBuffer));
+
+  while (array.length > 0) {
+    if (array.length < 24) {
+      // Incomplete package, must wait for more data.
+      return;
+    }
+
+    let command = getU32LE(array, 0);
+    let magic = getU32LE(array, 20);
+
+    if (command != ((magic ^ 0xffffffff) >>> 0)) {
+      console.error('adb message command vs magic failed.');
+      console.error('command = ' + command + ', magic = ' + magic);
+      return;
+    }
+
+    let payloadLength = getU32LE(array, 12);
+
+    if (array.length < 24 + payloadLength) {
+      // Incomplete package, must wait for more data.
+      return;
+    }
+
+    let payloadChecksum = getU32LE(array, 16);
+    let checksum = computeChecksum(array.slice(24));
+
+    if (payloadChecksum != checksum) {
+      console.error('adb message checksum mismatch.');
+      // This can happen if a shell command executes while another
+      // channel is receiving data.
+    }
+
+    switch (command) {
+      case A_CNXN: {
+        console.info('WebRTC adb connected.');
+        connected();
+        break;
+      }
+
+      case A_OKAY: {
+        let remoteId = getU32LE(array, 4);
+        console.debug('WebRTC adb channel created w/ remoteId ' + remoteId);
+        connected();
+        break;
+      }
+
+      case A_WRTE: {
+        let remoteId = getU32LE(array, 4);
+        adbSendOkay(remoteId);
+        break;
+      }
+    }
+    array = array.subarray(24 + payloadLength, array.length);
+  }
+}
+
+function init_adb(devConn, ccb = connectedCb, dcb = disconnectedCb) {
+  if (adb_ws) return;
+
+  adb_ws = {
+    send: function(buffer) {
+      devConn.sendAdbMessage(buffer);
+    }
+  };
+  connectedCb = ccb;
+  disconnectedCb = dcb;
+  awaitConnection();
+
+  devConn.onAdbMessage(msg => adbOnMessage(msg));
+
+  adbOpenConnection();
+}
diff --git a/host/frontend/webrtc/client/js/app.js b/host/frontend/webrtc/client/js/app.js
new file mode 100644
index 0000000..f26862a
--- /dev/null
+++ b/host/frontend/webrtc/client/js/app.js
@@ -0,0 +1,1031 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+async function ConnectDevice(deviceId, serverConnector) {
+  console.debug('Connect: ' + deviceId);
+  // Prepare messages in case of connection failure
+  let connectionAttemptDuration = 0;
+  const intervalMs = 15000;
+  let connectionInterval = setInterval(() => {
+    connectionAttemptDuration += intervalMs;
+    if (connectionAttemptDuration > 30000) {
+      showError(
+          'Connection should have occurred by now. ' +
+          'Please attempt to restart the guest device.');
+      clearInterval(connectionInterval);
+    } else if (connectionAttemptDuration > 15000) {
+      showWarning('Connection is taking longer than expected');
+    }
+  }, intervalMs);
+
+  let module = await import('./cf_webrtc.js');
+  let deviceConnection = await module.Connect(deviceId, serverConnector);
+  console.info('Connected to ' + deviceId);
+  clearInterval(connectionInterval);
+  return deviceConnection;
+}
+
+function setupMessages() {
+  let closeBtn = document.querySelector('#error-message .close-btn');
+  closeBtn.addEventListener('click', evt => {
+    evt.target.parentElement.className = 'hidden';
+  });
+}
+
+function showMessage(msg, className) {
+  let element = document.getElementById('error-message');
+  if (element.childNodes.length < 2) {
+    // First time, no text node yet
+    element.insertAdjacentText('afterBegin', msg);
+  } else {
+    element.childNodes[0].data = msg;
+  }
+  element.className = className;
+}
+
+function showWarning(msg) {
+  showMessage(msg, 'warning');
+}
+
+function showError(msg) {
+  showMessage(msg, 'error');
+}
+
+
+class DeviceDetailsUpdater {
+  #element;
+
+  constructor() {
+    this.#element = document.getElementById('device-details-hardware');
+  }
+
+  setHardwareDetailsText(text) {
+    this.#element.dataset.hardwareDetailsText = text;
+    return this;
+  }
+
+  setDeviceStateDetailsText(text) {
+    this.#element.dataset.deviceStateDetailsText = text;
+    return this;
+  }
+
+  update() {
+    this.#element.textContent =
+        [
+          this.#element.dataset.hardwareDetailsText,
+          this.#element.dataset.deviceStateDetailsText,
+        ].filter(e => e /*remove empty*/)
+            .join('\n');
+  }
+}  // DeviceDetailsUpdater
+
+class DeviceControlApp {
+  #deviceConnection = {};
+  #currentRotation = 0;
+  #displayDescriptions = [];
+  #buttons = {};
+  #recording = {};
+  #phys = {};
+  #deviceCount = 0;
+
+  constructor(deviceConnection) {
+    this.#deviceConnection = deviceConnection;
+  }
+
+  start() {
+    console.debug('Device description: ', this.#deviceConnection.description);
+    this.#deviceConnection.onControlMessage(msg => this.#onControlMessage(msg));
+    createToggleControl(
+        document.getElementById('keyboard-capture-control'), 'keyboard',
+        enabled => this.#onKeyboardCaptureToggle(enabled));
+    createToggleControl(
+        document.getElementById('mic-capture-control'), 'mic',
+        enabled => this.#onMicCaptureToggle(enabled));
+    createToggleControl(
+        document.getElementById('camera-control'), 'videocam',
+        enabled => this.#onCameraCaptureToggle(enabled));
+    createToggleControl(
+        document.getElementById('record-video-control'), 'movie_creation',
+        enabled => this.#onVideoCaptureToggle(enabled));
+    const audioElm = document.getElementById('device-audio');
+
+    let audioPlaybackCtrl = createToggleControl(
+        document.getElementById('audio-playback-control'), 'speaker',
+        enabled => this.#onAudioPlaybackToggle(enabled), !audioElm.paused);
+    // The audio element may start or stop playing at any time, this ensures the
+    // audio control always show the right state.
+    audioElm.onplay = () => audioPlaybackCtrl.Set(true);
+    audioElm.onpause = () => audioPlaybackCtrl.Set(false);
+
+    this.#showDeviceUI();
+  }
+
+  #showDeviceUI() {
+    window.onresize = evt => this.#resizeDeviceDisplays();
+    // Set up control panel buttons
+    this.#buttons = {};
+    this.#buttons['power'] = createControlPanelButton(
+        'power', 'Power', 'power_settings_new',
+        evt => this.#onControlPanelButton(evt));
+    this.#buttons['home'] = createControlPanelButton(
+        'home', 'Home', 'home', evt => this.#onControlPanelButton(evt));
+    this.#buttons['menu'] = createControlPanelButton(
+        'menu', 'Menu', 'menu', evt => this.#onControlPanelButton(evt));
+    this.#buttons['rotate'] = createControlPanelButton(
+        'rotate', 'Rotate', 'screen_rotation',
+        evt => this.#onRotateButton(evt));
+    this.#buttons['rotate'].adb = true;
+    this.#buttons['volumedown'] = createControlPanelButton(
+        'volumedown', 'Volume Down', 'volume_down',
+        evt => this.#onControlPanelButton(evt));
+    this.#buttons['volumeup'] = createControlPanelButton(
+        'volumeup', 'Volume Up', 'volume_up',
+        evt => this.#onControlPanelButton(evt));
+
+    createModalButton(
+        'device-details-button', 'device-details-modal',
+        'device-details-close');
+    createModalButton(
+        'bluetooth-modal-button', 'bluetooth-prompt',
+        'bluetooth-prompt-close');
+    createModalButton(
+        'bluetooth-prompt-wizard', 'bluetooth-wizard',
+        'bluetooth-wizard-close', 'bluetooth-prompt');
+    createModalButton(
+        'bluetooth-wizard-device', 'bluetooth-wizard-confirm',
+        'bluetooth-wizard-confirm-close', 'bluetooth-wizard');
+    createModalButton(
+        'bluetooth-wizard-another', 'bluetooth-wizard',
+        'bluetooth-wizard-close', 'bluetooth-wizard-confirm');
+    createModalButton(
+        'bluetooth-prompt-list', 'bluetooth-list',
+        'bluetooth-list-close', 'bluetooth-prompt');
+    createModalButton(
+        'bluetooth-prompt-console', 'bluetooth-console',
+        'bluetooth-console-close', 'bluetooth-prompt');
+    createModalButton(
+        'bluetooth-wizard-cancel', 'bluetooth-prompt',
+        'bluetooth-wizard-close', 'bluetooth-wizard');
+
+    positionModal('device-details-button', 'bluetooth-modal');
+    positionModal('device-details-button', 'bluetooth-prompt');
+    positionModal('device-details-button', 'bluetooth-wizard');
+    positionModal('device-details-button', 'bluetooth-wizard-confirm');
+    positionModal('device-details-button', 'bluetooth-list');
+    positionModal('device-details-button', 'bluetooth-console');
+
+    createButtonListener('bluetooth-prompt-list', null, this.#deviceConnection,
+      evt => this.#onRootCanalCommand(this.#deviceConnection, "list", evt));
+    createButtonListener('bluetooth-wizard-device', null, this.#deviceConnection,
+      evt => this.#onRootCanalCommand(this.#deviceConnection, "add", evt));
+    createButtonListener('bluetooth-list-trash', null, this.#deviceConnection,
+      evt => this.#onRootCanalCommand(this.#deviceConnection, "del", evt));
+    createButtonListener('bluetooth-prompt-wizard', null, this.#deviceConnection,
+      evt => this.#onRootCanalCommand(this.#deviceConnection, "list", evt));
+    createButtonListener('bluetooth-wizard-another', null, this.#deviceConnection,
+      evt => this.#onRootCanalCommand(this.#deviceConnection, "list", evt));
+
+    if (this.#deviceConnection.description.custom_control_panel_buttons.length >
+        0) {
+      document.getElementById('control-panel-custom-buttons').style.display =
+          'flex';
+      for (const button of this.#deviceConnection.description
+               .custom_control_panel_buttons) {
+        if (button.shell_command) {
+          // This button's command is handled by sending an ADB shell command.
+          this.#buttons[button.command] = createControlPanelButton(
+              button.command, button.title, button.icon_name,
+              e => this.#onCustomShellButton(button.shell_command, e),
+              'control-panel-custom-buttons');
+          this.#buttons[button.command].adb = true;
+        } else if (button.device_states) {
+          // This button corresponds to variable hardware device state(s).
+          this.#buttons[button.command] = createControlPanelButton(
+              button.command, button.title, button.icon_name,
+              this.#getCustomDeviceStateButtonCb(button.device_states),
+              'control-panel-custom-buttons');
+          for (const device_state of button.device_states) {
+            // hinge_angle is currently injected via an adb shell command that
+            // triggers a guest binary.
+            if ('hinge_angle_value' in device_state) {
+              this.#buttons[button.command].adb = true;
+            }
+          }
+        } else {
+          // This button's command is handled by custom action server.
+          this.#buttons[button.command] = createControlPanelButton(
+              button.command, button.title, button.icon_name,
+              evt => this.#onControlPanelButton(evt),
+              'control-panel-custom-buttons');
+        }
+      }
+    }
+
+    // Set up displays
+    this.#createDeviceDisplays();
+
+    // Set up audio
+    const deviceAudio = document.getElementById('device-audio');
+    for (const audio_desc of this.#deviceConnection.description.audio_streams) {
+      let stream_id = audio_desc.stream_id;
+      this.#deviceConnection.getStream(stream_id)
+          .then(stream => {
+            deviceAudio.srcObject = stream;
+            let playPromise = deviceAudio.play();
+            if (playPromise !== undefined) {
+              playPromise.catch(error => {
+                showWarning(
+                    'Audio playback is disabled, click on the speaker control to activate it');
+              });
+            }
+          })
+          .catch(e => console.error('Unable to get audio stream: ', e));
+    }
+
+    // Set up touch input
+    this.#startMouseTracking();
+
+    this.#updateDeviceHardwareDetails(
+        this.#deviceConnection.description.hardware);
+
+    // Show the error message and disable buttons when the WebRTC connection
+    // fails.
+    this.#deviceConnection.onConnectionStateChange(state => {
+      if (state == 'disconnected' || state == 'failed') {
+        this.#showWebrtcError();
+      }
+    });
+
+    let bluetoothConsole =
+        cmdConsole('bluetooth-console-view', 'bluetooth-console-input');
+    bluetoothConsole.addCommandListener(cmd => {
+      let inputArr = cmd.split(' ');
+      let command = inputArr[0];
+      inputArr.shift();
+      let args = inputArr;
+      this.#deviceConnection.sendBluetoothMessage(
+          createRootcanalMessage(command, args));
+    });
+    this.#deviceConnection.onBluetoothMessage(msg => {
+      let decoded = decodeRootcanalMessage(msg);
+      let deviceCount = btUpdateDeviceList(decoded);
+      if (deviceCount > 0) {
+        this.#deviceCount = deviceCount;
+        createButtonListener('bluetooth-list-trash', null, this.#deviceConnection,
+           evt => this.#onRootCanalCommand(this.#deviceConnection, "del", evt));
+      }
+      btUpdateAdded(decoded);
+      let phyList = btParsePhys(decoded);
+      if (phyList) {
+        this.#phys = phyList;
+      }
+      bluetoothConsole.addLine(decoded);
+    });
+  }
+
+  #onRootCanalCommand(deviceConnection, cmd, evt) {
+    if (cmd == "list") {
+      deviceConnection.sendBluetoothMessage(createRootcanalMessage("list", []));
+    }
+    if (cmd == "del") {
+      let id = evt.srcElement.getAttribute("data-device-id");
+      deviceConnection.sendBluetoothMessage(createRootcanalMessage("del", [id]));
+      deviceConnection.sendBluetoothMessage(createRootcanalMessage("list", []));
+    }
+    if (cmd == "add") {
+      let name = document.getElementById('bluetooth-wizard-name').value;
+      let type = document.getElementById('bluetooth-wizard-type').value;
+      if (type == "remote_loopback") {
+        deviceConnection.sendBluetoothMessage(createRootcanalMessage("add", [type]));
+      } else {
+        let mac = document.getElementById('bluetooth-wizard-mac').value;
+        deviceConnection.sendBluetoothMessage(createRootcanalMessage("add", [type, mac]));
+      }
+      let phyId = this.#phys["LOW_ENERGY"].toString();
+      if (type == "remote_loopback") {
+        phyId = this.#phys["BR_EDR"].toString();
+      }
+      let devId = this.#deviceCount.toString();
+      this.#deviceCount++;
+      deviceConnection.sendBluetoothMessage(createRootcanalMessage("add_device_to_phy", [devId, phyId]));
+    }
+  }
+
+  #showWebrtcError() {
+    document.getElementById('status-message').className = 'error';
+    document.getElementById('status-message').textContent =
+        'No connection to the guest device. ' +
+        'Please ensure the WebRTC process on the host machine is active.';
+    document.getElementById('status-message').style.visibility = 'visible';
+    const deviceDisplays = document.getElementById('device-displays');
+    deviceDisplays.style.display = 'none';
+    for (const [_, button] of Object.entries(this.#buttons)) {
+      button.disabled = true;
+    }
+  }
+
+  #takePhoto() {
+    const imageCapture = this.#deviceConnection.imageCapture;
+    if (imageCapture) {
+      const photoSettings = {
+        imageWidth: this.#deviceConnection.cameraWidth,
+        imageHeight: this.#deviceConnection.cameraHeight
+      };
+      imageCapture.takePhoto(photoSettings)
+          .then(blob => blob.arrayBuffer())
+          .then(buffer => this.#deviceConnection.sendOrQueueCameraData(buffer))
+          .catch(error => console.error(error));
+    }
+  }
+
+  #getCustomDeviceStateButtonCb(device_states) {
+    let states = device_states;
+    let index = 0;
+    return e => {
+      if (e.type == 'mousedown') {
+        // Reset any overridden device state.
+        adbShell('cmd device_state state reset');
+        // Send a device_state message for the current state.
+        let message = {
+          command: 'device_state',
+          ...states[index],
+        };
+        this.#deviceConnection.sendControlMessage(JSON.stringify(message));
+        console.debug('Control message sent: ', JSON.stringify(message));
+        let lidSwitchOpen = null;
+        if ('lid_switch_open' in states[index]) {
+          lidSwitchOpen = states[index].lid_switch_open;
+        }
+        let hingeAngle = null;
+        if ('hinge_angle_value' in states[index]) {
+          hingeAngle = states[index].hinge_angle_value;
+          // TODO(b/181157794): Use a custom Sensor HAL for hinge_angle
+          // injection instead of this guest binary.
+          adbShell(
+              '/vendor/bin/cuttlefish_sensor_injection hinge_angle ' +
+              states[index].hinge_angle_value);
+        }
+        // Update the Device Details view.
+        this.#updateDeviceStateDetails(lidSwitchOpen, hingeAngle);
+        // Cycle to the next state.
+        index = (index + 1) % states.length;
+      }
+    }
+  }
+
+  #resizeDeviceDisplays() {
+    // Padding between displays.
+    const deviceDisplayWidthPadding = 10;
+    // Padding for the display info above each display video.
+    const deviceDisplayHeightPadding = 38;
+
+    let deviceDisplayList = document.getElementsByClassName('device-display');
+    let deviceDisplayVideoList =
+        document.getElementsByClassName('device-display-video');
+    let deviceDisplayInfoList =
+        document.getElementsByClassName('device-display-info');
+
+    const deviceDisplays = document.getElementById('device-displays');
+    const rotationDegrees = this.#getTransformRotation(deviceDisplays);
+    const rotationRadians = rotationDegrees * Math.PI / 180;
+
+    // Auto-scale the screen based on window size.
+    let availableWidth = deviceDisplays.clientWidth;
+    let availableHeight = deviceDisplays.clientHeight - deviceDisplayHeightPadding;
+
+    // Reserve space for padding between the displays.
+    availableWidth = availableWidth -
+        (this.#displayDescriptions.length * deviceDisplayWidthPadding);
+
+    // Loop once over all of the displays to compute the total space needed.
+    let neededWidth = 0;
+    let neededHeight = 0;
+    for (let i = 0; i < deviceDisplayList.length; i++) {
+      let deviceDisplayDescription = this.#displayDescriptions[i];
+      let deviceDisplayVideo = deviceDisplayVideoList[i];
+
+      const originalDisplayWidth = deviceDisplayDescription.x_res;
+      const originalDisplayHeight = deviceDisplayDescription.y_res;
+
+      const neededBoundingBoxWidth =
+          Math.abs(Math.cos(rotationRadians) * originalDisplayWidth) +
+          Math.abs(Math.sin(rotationRadians) * originalDisplayHeight);
+      const neededBoundingBoxHeight =
+          Math.abs(Math.sin(rotationRadians) * originalDisplayWidth) +
+          Math.abs(Math.cos(rotationRadians) * originalDisplayHeight);
+
+      neededWidth = neededWidth + neededBoundingBoxWidth;
+      neededHeight = Math.max(neededHeight, neededBoundingBoxHeight);
+    }
+
+    const scaling =
+        Math.min(availableWidth / neededWidth, availableHeight / neededHeight);
+
+    // Loop again over all of the displays to set the sizes and positions.
+    let deviceDisplayLeftOffset = 0;
+    for (let i = 0; i < deviceDisplayList.length; i++) {
+      let deviceDisplay = deviceDisplayList[i];
+      let deviceDisplayVideo = deviceDisplayVideoList[i];
+      let deviceDisplayInfo = deviceDisplayInfoList[i];
+      let deviceDisplayDescription = this.#displayDescriptions[i];
+
+      let rotated = this.#currentRotation == 1 ? ' (Rotated)' : '';
+      deviceDisplayInfo.textContent = `Display ${i} - ` +
+          `${deviceDisplayDescription.x_res}x` +
+          `${deviceDisplayDescription.y_res} ` +
+          `(${deviceDisplayDescription.dpi} DPI)${rotated}`;
+
+      const originalDisplayWidth = deviceDisplayDescription.x_res;
+      const originalDisplayHeight = deviceDisplayDescription.y_res;
+
+      const scaledDisplayWidth = originalDisplayWidth * scaling;
+      const scaledDisplayHeight = originalDisplayHeight * scaling;
+
+      const neededBoundingBoxWidth =
+          Math.abs(Math.cos(rotationRadians) * originalDisplayWidth) +
+          Math.abs(Math.sin(rotationRadians) * originalDisplayHeight);
+      const neededBoundingBoxHeight =
+          Math.abs(Math.sin(rotationRadians) * originalDisplayWidth) +
+          Math.abs(Math.cos(rotationRadians) * originalDisplayHeight);
+
+      const scaledBoundingBoxWidth = neededBoundingBoxWidth * scaling;
+      const scaledBoundingBoxHeight = neededBoundingBoxHeight * scaling;
+
+      const offsetX = (scaledBoundingBoxWidth - scaledDisplayWidth) / 2;
+      const offsetY = (scaledBoundingBoxHeight - scaledDisplayHeight) / 2;
+
+      deviceDisplayVideo.style.width = scaledDisplayWidth;
+      deviceDisplayVideo.style.height = scaledDisplayHeight;
+      deviceDisplayVideo.style.transform = `translateX(${offsetX}px) ` +
+          `translateY(${offsetY}px) ` +
+          `rotateZ(${rotationDegrees}deg) `;
+
+      deviceDisplay.style.left = `${deviceDisplayLeftOffset}px`;
+      deviceDisplay.style.width = scaledBoundingBoxWidth;
+      deviceDisplay.style.height = scaledBoundingBoxHeight;
+
+      deviceDisplayLeftOffset = deviceDisplayLeftOffset + deviceDisplayWidthPadding +
+          scaledBoundingBoxWidth;
+    }
+  }
+
+  #getTransformRotation(element) {
+    if (!element.style.textIndent) {
+      return 0;
+    }
+    // Remove 'px' and convert to float.
+    return parseFloat(element.style.textIndent.slice(0, -2));
+  }
+
+  #onControlMessage(message) {
+    let message_data = JSON.parse(message.data);
+    console.debug('Control message received: ', message_data)
+    let metadata = message_data.metadata;
+    if (message_data.event == 'VIRTUAL_DEVICE_BOOT_STARTED') {
+      // Start the adb connection after receiving the BOOT_STARTED message.
+      // (This is after the adbd start message. Attempting to connect
+      // immediately after adbd starts causes issues.)
+      this.#initializeAdb();
+    }
+    if (message_data.event == 'VIRTUAL_DEVICE_SCREEN_CHANGED') {
+      if (metadata.rotation != this.#currentRotation) {
+        // Animate the screen rotation.
+        const targetRotation = metadata.rotation == 0 ? 0 : -90;
+
+        $('#device-displays')
+            .animate(
+                {
+                  textIndent: targetRotation,
+                },
+                {
+                  duration: 1000,
+                  step: (now, tween) => {
+                    this.#resizeDeviceDisplays();
+                  },
+                });
+      }
+
+      this.#currentRotation = metadata.rotation;
+    }
+    if (message_data.event == 'VIRTUAL_DEVICE_CAPTURE_IMAGE') {
+      if (this.#deviceConnection.cameraEnabled) {
+        this.#takePhoto();
+      }
+    }
+    if (message_data.event == 'VIRTUAL_DEVICE_DISPLAY_POWER_MODE_CHANGED') {
+      this.#updateDisplayVisibility(metadata.display, metadata.mode);
+    }
+  }
+
+  #updateDeviceStateDetails(lidSwitchOpen, hingeAngle) {
+    let deviceStateDetailsTextLines = [];
+    if (lidSwitchOpen != null) {
+      let state = lidSwitchOpen ? 'Opened' : 'Closed';
+      deviceStateDetailsTextLines.push(`Lid Switch - ${state}`);
+    }
+    if (hingeAngle != null) {
+      deviceStateDetailsTextLines.push(`Hinge Angle - ${hingeAngle}`);
+    }
+    let deviceStateDetailsText = deviceStateDetailsTextLines.join('\n');
+    new DeviceDetailsUpdater()
+        .setDeviceStateDetailsText(deviceStateDetailsText)
+        .update();
+  }
+
+  #updateDeviceHardwareDetails(hardware) {
+    let hardwareDetailsTextLines = [];
+    Object.keys(hardware).forEach((key) => {
+      let value = hardware[key];
+      hardwareDetailsTextLines.push(`${key} - ${value}`);
+    });
+
+    let hardwareDetailsText = hardwareDetailsTextLines.join('\n');
+    new DeviceDetailsUpdater()
+        .setHardwareDetailsText(hardwareDetailsText)
+        .update();
+  }
+
+  // Creates a <video> element and a <div> container element for each display.
+  // The extra <div> container elements are used to maintain the width and
+  // height of the device as the CSS 'transform' property used on the <video>
+  // element for rotating the device only affects the visuals of the element
+  // and not its layout.
+  #createDeviceDisplays() {
+    console.debug(
+        'Display descriptions: ', this.#deviceConnection.description.displays);
+    this.#displayDescriptions = this.#deviceConnection.description.displays;
+    let anyDisplayLoaded = false;
+    const deviceDisplays = document.getElementById('device-displays');
+    for (const deviceDisplayDescription of this.#displayDescriptions) {
+      let deviceDisplay = document.createElement('div');
+      deviceDisplay.classList.add('device-display');
+      // Start the screen as hidden. Only show when data is ready.
+      deviceDisplay.style.visibility = 'hidden';
+
+      let deviceDisplayInfo = document.createElement("div");
+      deviceDisplayInfo.classList.add("device-display-info");
+      deviceDisplayInfo.id = deviceDisplayDescription.stream_id + '_info';
+      deviceDisplay.appendChild(deviceDisplayInfo);
+
+      let deviceDisplayVideo = document.createElement('video');
+      deviceDisplayVideo.autoplay = true;
+      deviceDisplayVideo.muted = true;
+      deviceDisplayVideo.id = deviceDisplayDescription.stream_id;
+      deviceDisplayVideo.classList.add('device-display-video');
+      deviceDisplayVideo.addEventListener('loadeddata', (evt) => {
+        if (!anyDisplayLoaded) {
+          anyDisplayLoaded = true;
+          this.#onDeviceDisplayLoaded();
+        }
+      });
+      deviceDisplay.appendChild(deviceDisplayVideo);
+
+      deviceDisplays.appendChild(deviceDisplay);
+
+      let stream_id = deviceDisplayDescription.stream_id;
+      this.#deviceConnection.getStream(stream_id)
+          .then(stream => {
+            deviceDisplayVideo.srcObject = stream;
+          })
+          .catch(e => console.error('Unable to get display stream: ', e));
+    }
+  }
+
+  #initializeAdb() {
+    init_adb(
+        this.#deviceConnection, () => this.#showAdbConnected(),
+        () => this.#showAdbError());
+  }
+
+  #showAdbConnected() {
+    // Screen changed messages are not reported until after boot has completed.
+    // Certain default adb buttons change screen state, so wait for boot
+    // completion before enabling these buttons.
+    document.getElementById('status-message').className = 'connected';
+    document.getElementById('status-message').textContent =
+        'adb connection established successfully.';
+    setTimeout(() => {
+      document.getElementById('status-message').style.visibility = 'hidden';
+    }, 5000);
+    for (const [_, button] of Object.entries(this.#buttons)) {
+      if (button.adb) {
+        button.disabled = false;
+      }
+    }
+  }
+
+  #showAdbError() {
+    document.getElementById('status-message').className = 'error';
+    document.getElementById('status-message').textContent =
+        'adb connection failed.';
+    document.getElementById('status-message').style.visibility = 'visible';
+    for (const [_, button] of Object.entries(this.#buttons)) {
+      if (button.adb) {
+        button.disabled = true;
+      }
+    }
+  }
+
+  #onDeviceDisplayLoaded() {
+    document.getElementById('status-message').textContent =
+        'Awaiting bootup and adb connection. Please wait...';
+    this.#resizeDeviceDisplays();
+
+    let deviceDisplayList = document.getElementsByClassName('device-display');
+    for (const deviceDisplay of deviceDisplayList) {
+      deviceDisplay.style.visibility = 'visible';
+    }
+
+    // Enable the buttons after the screen is visible.
+    for (const [key, button] of Object.entries(this.#buttons)) {
+      if (!button.adb) {
+        button.disabled = false;
+      }
+    }
+    // Start the adb connection if it is not already started.
+    this.#initializeAdb();
+  }
+
+  #onRotateButton(e) {
+    // Attempt to init adb again, in case the initial connection failed.
+    // This succeeds immediately if already connected.
+    this.#initializeAdb();
+    if (e.type == 'mousedown') {
+      adbShell(
+          '/vendor/bin/cuttlefish_sensor_injection rotate ' +
+          (this.#currentRotation == 0 ? 'landscape' : 'portrait'))
+    }
+  }
+
+  #onControlPanelButton(e) {
+    if (e.type == 'mouseout' && e.which == 0) {
+      // Ignore mouseout events if no mouse button is pressed.
+      return;
+    }
+    this.#deviceConnection.sendControlMessage(JSON.stringify({
+      command: e.target.dataset.command,
+      button_state: e.type == 'mousedown' ? 'down' : 'up',
+    }));
+  }
+
+  #onKeyboardCaptureToggle(enabled) {
+    if (enabled) {
+      document.addEventListener('keydown', evt => this.#onKeyEvent(evt));
+      document.addEventListener('keyup', evt => this.#onKeyEvent(evt));
+    } else {
+      document.removeEventListener('keydown', evt => this.#onKeyEvent(evt));
+      document.removeEventListener('keyup', evt => this.#onKeyEvent(evt));
+    }
+  }
+
+  #onKeyEvent(e) {
+    e.preventDefault();
+    this.#deviceConnection.sendKeyEvent(e.code, e.type);
+  }
+
+  #startMouseTracking() {
+    let $this = this;
+    let mouseIsDown = false;
+    let mouseCtx = {
+      down: false,
+      touchIdSlotMap: new Map(),
+      touchSlots: [],
+    };
+    function onStartDrag(e) {
+      e.preventDefault();
+
+      // console.debug("mousedown at " + e.pageX + " / " + e.pageY);
+      mouseCtx.down = true;
+
+      $this.#sendEventUpdate(mouseCtx, e);
+    }
+
+    function onEndDrag(e) {
+      e.preventDefault();
+
+      // console.debug("mouseup at " + e.pageX + " / " + e.pageY);
+      mouseCtx.down = false;
+
+      $this.#sendEventUpdate(mouseCtx, e);
+    }
+
+    function onContinueDrag(e) {
+      e.preventDefault();
+
+      // console.debug("mousemove at " + e.pageX + " / " + e.pageY + ", down=" +
+      // mouseIsDown);
+      if (mouseCtx.down) {
+        $this.#sendEventUpdate(mouseCtx, e);
+      }
+    }
+
+    let deviceDisplayList = document.getElementsByClassName('device-display');
+    if (window.PointerEvent) {
+      for (const deviceDisplay of deviceDisplayList) {
+        deviceDisplay.addEventListener('pointerdown', onStartDrag);
+        deviceDisplay.addEventListener('pointermove', onContinueDrag);
+        deviceDisplay.addEventListener('pointerup', onEndDrag);
+      }
+    } else if (window.TouchEvent) {
+      for (const deviceDisplay of deviceDisplayList) {
+        deviceDisplay.addEventListener('touchstart', onStartDrag);
+        deviceDisplay.addEventListener('touchmove', onContinueDrag);
+        deviceDisplay.addEventListener('touchend', onEndDrag);
+      }
+    } else if (window.MouseEvent) {
+      for (const deviceDisplay of deviceDisplayList) {
+        deviceDisplay.addEventListener('mousedown', onStartDrag);
+        deviceDisplay.addEventListener('mousemove', onContinueDrag);
+        deviceDisplay.addEventListener('mouseup', onEndDrag);
+      }
+    }
+  }
+
+  #sendEventUpdate(ctx, e) {
+    let eventType = e.type.substring(0, 5);
+
+    // The <video> element:
+    const deviceDisplay = e.target;
+
+    // Before the first video frame arrives there is no way to know width and
+    // height of the device's screen, so turn every click into a click at 0x0.
+    // A click at that position is not more dangerous than anywhere else since
+    // the user is clicking blind anyways.
+    const videoWidth = deviceDisplay.videoWidth ? deviceDisplay.videoWidth : 1;
+    const videoHeight =
+        deviceDisplay.videoHeight ? deviceDisplay.videoHeight : 1;
+    const elementWidth =
+        deviceDisplay.offsetWidth ? deviceDisplay.offsetWidth : 1;
+    const elementHeight =
+        deviceDisplay.offsetHeight ? deviceDisplay.offsetHeight : 1;
+
+    // vh*ew > eh*vw? then scale h instead of w
+    const scaleHeight = videoHeight * elementWidth > videoWidth * elementHeight;
+    let elementScaling = 0, videoScaling = 0;
+    if (scaleHeight) {
+      elementScaling = elementHeight;
+      videoScaling = videoHeight;
+    } else {
+      elementScaling = elementWidth;
+      videoScaling = videoWidth;
+    }
+
+    // The screen uses the 'object-fit: cover' property in order to completely
+    // fill the element while maintaining the screen content's aspect ratio.
+    // Therefore:
+    // - If vh*ew > eh*vw, w is scaled so that content width == element width
+    // - Otherwise,        h is scaled so that content height == element height
+    const scaleWidth = videoHeight * elementWidth > videoWidth * elementHeight;
+
+    // Convert to coordinates relative to the video by scaling.
+    // (This matches the scaling used by 'object-fit: cover'.)
+    //
+    // This scaling is needed to translate from the in-browser x/y to the
+    // on-device x/y.
+    //   - When the device screen has not been resized, this is simple: scale
+    //     the coordinates based on the ratio between the input video size and
+    //     the in-browser size.
+    //   - When the device screen has been resized, this scaling is still needed
+    //     even though the in-browser size and device size are identical. This
+    //     is due to the way WindowManager handles a resized screen, resized via
+    //     `adb shell wm size`:
+    //       - The ABS_X and ABS_Y max values of the screen retain their
+    //         original values equal to the value set when launching the device
+    //         (which equals the video size here).
+    //       - The sent ABS_X and ABS_Y values need to be scaled based on the
+    //         ratio between the max size (video size) and in-browser size.
+    const scaling =
+        scaleWidth ? videoWidth / elementWidth : videoHeight / elementHeight;
+
+    let xArr = [];
+    let yArr = [];
+    let idArr = [];
+    let slotArr = [];
+
+    if (eventType == 'mouse' || eventType == 'point') {
+      xArr.push(e.offsetX);
+      yArr.push(e.offsetY);
+
+      let thisId = -1;
+      if (eventType == 'point') {
+        thisId = e.pointerId;
+      }
+
+      slotArr.push(0);
+      idArr.push(thisId);
+    } else if (eventType == 'touch') {
+      // touchstart: list of touch points that became active
+      // touchmove: list of touch points that changed
+      // touchend: list of touch points that were removed
+      let changes = e.changedTouches;
+      let rect = e.target.getBoundingClientRect();
+      for (let i = 0; i < changes.length; i++) {
+        xArr.push(changes[i].pageX - rect.left);
+        yArr.push(changes[i].pageY - rect.top);
+        if (ctx.touchIdSlotMap.has(changes[i].identifier)) {
+          let slot = ctx.touchIdSlotMap.get(changes[i].identifier);
+
+          slotArr.push(slot);
+          if (e.type == 'touchstart') {
+            // error
+            console.error('touchstart when already have slot');
+            return;
+          } else if (e.type == 'touchmove') {
+            idArr.push(changes[i].identifier);
+          } else if (e.type == 'touchend') {
+            ctx.touchSlots[slot] = false;
+            ctx.touchIdSlotMap.delete(changes[i].identifier);
+            idArr.push(-1);
+          }
+        } else {
+          if (e.type == 'touchstart') {
+            let slot = -1;
+            for (let j = 0; j < ctx.touchSlots.length; j++) {
+              if (!ctx.touchSlots[j]) {
+                slot = j;
+                break;
+              }
+            }
+            if (slot == -1) {
+              slot = ctx.touchSlots.length;
+              ctx.touchSlots.push(true);
+            }
+            slotArr.push(slot);
+            ctx.touchSlots[slot] = true;
+            ctx.touchIdSlotMap.set(changes[i].identifier, slot);
+            idArr.push(changes[i].identifier);
+          } else if (e.type == 'touchmove') {
+            // error
+            console.error('touchmove when no slot');
+            return;
+          } else if (e.type == 'touchend') {
+            // error
+            console.error('touchend when no slot');
+            return;
+          }
+        }
+      }
+    }
+
+    for (let i = 0; i < xArr.length; i++) {
+      xArr[i] = xArr[i] * scaling;
+      yArr[i] = yArr[i] * scaling;
+
+      // Substract the offset produced by the difference in aspect ratio, if
+      // any.
+      if (scaleWidth) {
+        // Width was scaled, leaving excess content height, so subtract from y.
+        yArr[i] -= (elementHeight * scaling - videoHeight) / 2;
+      } else {
+        // Height was scaled, leaving excess content width, so subtract from x.
+        xArr[i] -= (elementWidth * scaling - videoWidth) / 2;
+      }
+
+      xArr[i] = Math.trunc(xArr[i]);
+      yArr[i] = Math.trunc(yArr[i]);
+    }
+
+    // NOTE: Rotation is handled automatically because the CSS rotation through
+    // transforms also rotates the coordinates of events on the object.
+
+    const display_label = deviceDisplay.id;
+
+    this.#deviceConnection.sendMultiTouch(
+        {idArr, xArr, yArr, down: ctx.down, slotArr, display_label});
+  }
+
+  #updateDisplayVisibility(displayId, powerMode) {
+    const display = document.getElementById('display_' + displayId).parentElement;
+    if (display == null) {
+      console.error('Unknown display id: ' + displayId);
+      return;
+    }
+    powerMode = powerMode.toLowerCase();
+    switch (powerMode) {
+      case 'on':
+        display.style.visibility = 'visible';
+        break;
+      case 'off':
+        display.style.visibility = 'hidden';
+        break;
+      default:
+        console.error('Display ' + displayId + ' has unknown display power mode: ' + powerMode);
+    }
+  }
+
+  #onMicCaptureToggle(enabled) {
+    return this.#deviceConnection.useMic(enabled);
+  }
+
+  #onCameraCaptureToggle(enabled) {
+    return this.#deviceConnection.useCamera(enabled);
+  }
+
+  #getZeroPaddedString(value, desiredLength) {
+    const s = String(value);
+    return '0'.repeat(desiredLength - s.length) + s;
+  }
+
+  #getTimestampString() {
+    const now = new Date();
+    return [
+      now.getFullYear(),
+      this.#getZeroPaddedString(now.getMonth(), 2),
+      this.#getZeroPaddedString(now.getDay(), 2),
+      this.#getZeroPaddedString(now.getHours(), 2),
+      this.#getZeroPaddedString(now.getMinutes(), 2),
+      this.#getZeroPaddedString(now.getSeconds(), 2),
+    ].join('_');
+  }
+
+  #onVideoCaptureToggle(enabled) {
+    const recordToggle = document.getElementById('record-video-control');
+    if (enabled) {
+      let recorders = [];
+
+      const timestamp = this.#getTimestampString();
+
+      let deviceDisplayVideoList =
+        document.getElementsByClassName('device-display-video');
+      for (let i = 0; i < deviceDisplayVideoList.length; i++) {
+        const deviceDisplayVideo = deviceDisplayVideoList[i];
+
+        const recorder = new MediaRecorder(deviceDisplayVideo.captureStream());
+        const recordedData = [];
+
+        recorder.ondataavailable = event => recordedData.push(event.data);
+        recorder.onstop = event => {
+          const recording = new Blob(recordedData, { type: "video/webm" });
+
+          const downloadLink = document.createElement('a');
+          downloadLink.setAttribute('download', timestamp + '_display_' + i + '.webm');
+          downloadLink.setAttribute('href', URL.createObjectURL(recording));
+          downloadLink.click();
+        };
+
+        recorder.start();
+        recorders.push(recorder);
+      }
+      this.#recording['recorders'] = recorders;
+
+      recordToggle.style.backgroundColor = 'red';
+    } else {
+      for (const recorder of this.#recording['recorders']) {
+        recorder.stop();
+      }
+      recordToggle.style.backgroundColor = '';
+    }
+    return Promise.resolve(enabled);
+  }
+
+  #onAudioPlaybackToggle(enabled) {
+    const audioElem = document.getElementById('device-audio');
+    if (enabled) {
+      audioElem.play();
+    } else {
+      audioElem.pause();
+    }
+  }
+
+  #onCustomShellButton(shell_command, e) {
+    // Attempt to init adb again, in case the initial connection failed.
+    // This succeeds immediately if already connected.
+    this.#initializeAdb();
+    if (e.type == 'mousedown') {
+      adbShell(shell_command);
+    }
+  }
+}  // DeviceControlApp
+
+window.addEventListener("load", async evt => {
+  try {
+    setupMessages();
+    let connectorModule = await import('./server_connector.js');
+    let deviceConnection = await ConnectDevice(
+        connectorModule.deviceId(), await connectorModule.createConnector());
+    let deviceControlApp = new DeviceControlApp(deviceConnection);
+    deviceControlApp.start();
+    document.getElementById('device-connection').style.display = 'block';
+  } catch(err) {
+    console.error('Unable to connect: ', err);
+    showError(
+      'No connection to the guest device. ' +
+      'Please ensure the WebRTC process on the host machine is active.');
+  }
+  document.getElementById('loader').style.display = 'none';
+});
diff --git a/host/frontend/webrtc/client/js/cf_webrtc.js b/host/frontend/webrtc/client/js/cf_webrtc.js
new file mode 100644
index 0000000..5c91383
--- /dev/null
+++ b/host/frontend/webrtc/client/js/cf_webrtc.js
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function createDataChannel(pc, label, onMessage) {
+  console.debug('creating data channel: ' + label);
+  let dataChannel = pc.createDataChannel(label);
+  // Return an object with a send function like that of the dataChannel, but
+  // that only actually sends over the data channel once it has connected.
+  return {
+    channelPromise: new Promise((resolve, reject) => {
+      dataChannel.onopen = (event) => {
+        resolve(dataChannel);
+      };
+      dataChannel.onclose = () => {
+        console.debug(
+            'Data channel=' + label + ' state=' + dataChannel.readyState);
+      };
+      dataChannel.onmessage = onMessage ? onMessage : (msg) => {
+        console.debug('Data channel=' + label + ' data="' + msg.data + '"');
+      };
+      dataChannel.onerror = err => {
+        reject(err);
+      };
+    }),
+    send: function(msg) {
+      this.channelPromise = this.channelPromise.then(channel => {
+        channel.send(msg);
+        return channel;
+      })
+    },
+  };
+}
+
+function awaitDataChannel(pc, label, onMessage) {
+  console.debug('expecting data channel: ' + label);
+  // Return an object with a send function like that of the dataChannel, but
+  // that only actually sends over the data channel once it has connected.
+  return {
+    channelPromise: new Promise((resolve, reject) => {
+      let prev_ondatachannel = pc.ondatachannel;
+      pc.ondatachannel = ev => {
+        let dataChannel = ev.channel;
+        if (dataChannel.label == label) {
+          dataChannel.onopen = (event) => {
+            resolve(dataChannel);
+          };
+          dataChannel.onclose = () => {
+            console.debug(
+                'Data channel=' + label + ' state=' + dataChannel.readyState);
+          };
+          dataChannel.onmessage = onMessage ? onMessage : (msg) => {
+            console.debug('Data channel=' + label + ' data="' + msg.data + '"');
+          };
+          dataChannel.onerror = err => {
+            reject(err);
+          };
+        } else if (prev_ondatachannel) {
+          prev_ondatachannel(ev);
+        }
+      };
+    }),
+    send: function(msg) {
+      this.channelPromise = this.channelPromise.then(channel => {
+        channel.send(msg);
+        return channel;
+      })
+    },
+  };
+}
+
+class DeviceConnection {
+  #pc;
+  #control;
+  #description;
+
+  #cameraDataChannel;
+  #cameraInputQueue;
+  #controlChannel;
+  #inputChannel;
+  #adbChannel;
+  #bluetoothChannel;
+
+  #streams;
+  #streamPromiseResolvers;
+  #micSenders = [];
+  #cameraSenders = [];
+  #camera_res_x;
+  #camera_res_y;
+
+  #onAdbMessage;
+  #onControlMessage;
+  #onBluetoothMessage;
+
+  constructor(pc, control) {
+    this.#pc = pc;
+    this.#control = control;
+    this.#cameraDataChannel = pc.createDataChannel('camera-data-channel');
+    this.#cameraDataChannel.binaryType = 'arraybuffer';
+    this.#cameraInputQueue = new Array();
+    var self = this;
+    this.#cameraDataChannel.onbufferedamountlow = () => {
+      if (self.#cameraInputQueue.length > 0) {
+        self.sendCameraData(self.#cameraInputQueue.shift());
+      }
+    };
+    this.#inputChannel = createDataChannel(pc, 'input-channel');
+    this.#adbChannel = createDataChannel(pc, 'adb-channel', (msg) => {
+      if (this.#onAdbMessage) {
+        this.#onAdbMessage(msg.data);
+      } else {
+        console.error('Received unexpected ADB message');
+      }
+    });
+    this.#controlChannel = awaitDataChannel(pc, 'device-control', (msg) => {
+      if (this.#onControlMessage) {
+        this.#onControlMessage(msg);
+      } else {
+        console.error('Received unexpected Control message');
+      }
+    });
+    this.#bluetoothChannel =
+        createDataChannel(pc, 'bluetooth-channel', (msg) => {
+          if (this.#onBluetoothMessage) {
+            this.#onBluetoothMessage(msg.data);
+          } else {
+            console.error('Received unexpected Bluetooth message');
+          }
+        });
+    this.#streams = {};
+    this.#streamPromiseResolvers = {};
+
+    pc.addEventListener('track', e => {
+      console.debug('Got remote stream: ', e);
+      for (const stream of e.streams) {
+        this.#streams[stream.id] = stream;
+        if (this.#streamPromiseResolvers[stream.id]) {
+          for (let resolver of this.#streamPromiseResolvers[stream.id]) {
+            resolver();
+          }
+          delete this.#streamPromiseResolvers[stream.id];
+        }
+      }
+    });
+  }
+
+  set description(desc) {
+    this.#description = desc;
+  }
+
+  get description() {
+    return this.#description;
+  }
+
+  get imageCapture() {
+    if (this.#cameraSenders && this.#cameraSenders.length > 0) {
+      let track = this.#cameraSenders[0].track;
+      return new ImageCapture(track);
+    }
+    return undefined;
+  }
+
+  get cameraWidth() {
+    return this.#camera_res_x;
+  }
+
+  get cameraHeight() {
+    return this.#camera_res_y;
+  }
+
+  get cameraEnabled() {
+    return this.#cameraSenders && this.#cameraSenders.length > 0;
+  }
+
+  getStream(stream_id) {
+    return new Promise((resolve, reject) => {
+      if (this.#streams[stream_id]) {
+        resolve(this.#streams[stream_id]);
+      } else {
+        if (!this.#streamPromiseResolvers[stream_id]) {
+          this.#streamPromiseResolvers[stream_id] = [];
+        }
+        this.#streamPromiseResolvers[stream_id].push(resolve);
+      }
+    });
+  }
+
+  #sendJsonInput(evt) {
+    this.#inputChannel.send(JSON.stringify(evt));
+  }
+
+  sendMousePosition({x, y, down, display_label}) {
+    this.#sendJsonInput({
+      type: 'mouse',
+      down: down ? 1 : 0,
+      x,
+      y,
+      display_label,
+    });
+  }
+
+  // TODO (b/124121375): This should probably be an array of pointer events and
+  // have different properties.
+  sendMultiTouch({idArr, xArr, yArr, down, slotArr, display_label}) {
+    this.#sendJsonInput({
+      type: 'multi-touch',
+      id: idArr,
+      x: xArr,
+      y: yArr,
+      down: down ? 1 : 0,
+      slot: slotArr,
+      display_label: display_label,
+    });
+  }
+
+  sendKeyEvent(code, type) {
+    this.#sendJsonInput({type: 'keyboard', keycode: code, event_type: type});
+  }
+
+  disconnect() {
+    this.#pc.close();
+  }
+
+  // Sends binary data directly to the in-device adb daemon (skipping the host)
+  sendAdbMessage(msg) {
+    this.#adbChannel.send(msg);
+  }
+
+  // Provide a callback to receive data from the in-device adb daemon
+  onAdbMessage(cb) {
+    this.#onAdbMessage = cb;
+  }
+
+  // Send control commands to the device
+  sendControlMessage(msg) {
+    this.#controlChannel.send(msg);
+  }
+
+  async #useDevice(in_use, senders_arr, device_opt) {
+    // An empty array means no tracks are currently in use
+    if (senders_arr.length > 0 === !!in_use) {
+      console.warn('Device is already ' + (in_use ? '' : 'not ') + 'in use');
+      return in_use;
+    }
+    let renegotiation_needed = false;
+    if (in_use) {
+      try {
+        let stream = await navigator.mediaDevices.getUserMedia(device_opt);
+        stream.getTracks().forEach(track => {
+          console.info(`Using ${track.kind} device: ${track.label}`);
+          senders_arr.push(this.#pc.addTrack(track));
+          renegotiation_needed = true;
+        });
+      } catch (e) {
+        console.error('Failed to add stream to peer connection: ', e);
+        // Don't return yet, if there were errors some tracks may have been
+        // added so the connection should be renegotiated again.
+      }
+    } else {
+      for (const sender of senders_arr) {
+        console.info(
+            `Removing ${sender.track.kind} device: ${sender.track.label}`);
+        let track = sender.track;
+        track.stop();
+        this.#pc.removeTrack(sender);
+        renegotiation_needed = true;
+      }
+      // Empty the array passed by reference, just assigning [] won't do that.
+      senders_arr.length = 0;
+    }
+    if (renegotiation_needed) {
+      this.#control.renegotiateConnection();
+    }
+    // Return the new state
+    return senders_arr.length > 0;
+  }
+
+  async useMic(in_use) {
+    return this.#useDevice(in_use, this.#micSenders, {audio: true, video: false});
+  }
+
+  async useCamera(in_use) {
+    return this.#useDevice(in_use, this.#micSenders, {audio: false, video: true});
+  }
+
+  sendCameraResolution(stream) {
+    const cameraTracks = stream.getVideoTracks();
+    if (cameraTracks.length > 0) {
+      const settings = cameraTracks[0].getSettings();
+      this.#camera_res_x = settings.width;
+      this.#camera_res_y = settings.height;
+      this.sendControlMessage(JSON.stringify({
+        command: 'camera_settings',
+        width: settings.width,
+        height: settings.height,
+        frame_rate: settings.frameRate,
+        facing: settings.facingMode
+      }));
+    }
+  }
+
+  sendOrQueueCameraData(data) {
+    if (this.#cameraDataChannel.bufferedAmount > 0 ||
+        this.#cameraInputQueue.length > 0) {
+      this.#cameraInputQueue.push(data);
+    } else {
+      this.sendCameraData(data);
+    }
+  }
+
+  sendCameraData(data) {
+    const MAX_SIZE = 65535;
+    const END_MARKER = 'EOF';
+    for (let i = 0; i < data.byteLength; i += MAX_SIZE) {
+      // range is clamped to the valid index range
+      this.#cameraDataChannel.send(data.slice(i, i + MAX_SIZE));
+    }
+    this.#cameraDataChannel.send(END_MARKER);
+  }
+
+  // Provide a callback to receive control-related comms from the device
+  onControlMessage(cb) {
+    this.#onControlMessage = cb;
+  }
+
+  sendBluetoothMessage(msg) {
+    this.#bluetoothChannel.send(msg);
+  }
+
+  onBluetoothMessage(cb) {
+    this.#onBluetoothMessage = cb;
+  }
+
+  // Provide a callback to receive connectionstatechange states.
+  onConnectionStateChange(cb) {
+    this.#pc.addEventListener(
+        'connectionstatechange', evt => cb(this.#pc.connectionState));
+  }
+}
+
+class Controller {
+  #pc;
+  #serverConnector;
+
+  constructor(serverConnector) {
+    this.#serverConnector = serverConnector;
+    serverConnector.onDeviceMsg(msg => this.#onDeviceMessage(msg));
+  }
+
+  #onDeviceMessage(message) {
+    let type = message.type;
+    switch (type) {
+      case 'offer':
+        this.#onOffer({type: 'offer', sdp: message.sdp});
+        break;
+      case 'answer':
+        this.#onAnswer({type: 'answer', sdp: message.sdp});
+        break;
+      case 'ice-candidate':
+          this.#onIceCandidate(new RTCIceCandidate({
+            sdpMid: message.mid,
+            sdpMLineIndex: message.mLineIndex,
+            candidate: message.candidate
+          }));
+        break;
+      case 'error':
+        console.error('Device responded with error message: ', message.error);
+        break;
+      default:
+        console.error('Unrecognized message type from device: ', type);
+    }
+  }
+
+  async #sendClientDescription(desc) {
+    console.debug('sendClientDescription');
+    return this.#serverConnector.sendToDevice({type: 'answer', sdp: desc.sdp});
+  }
+
+  async #sendIceCandidate(candidate) {
+    console.debug('sendIceCandidate');
+    return this.#serverConnector.sendToDevice({type: 'ice-candidate', candidate});
+  }
+
+  async #onOffer(desc) {
+    console.debug('Remote description (offer): ', desc);
+    try {
+      await this.#pc.setRemoteDescription(desc);
+      let answer = await this.#pc.createAnswer();
+      console.debug('Answer: ', answer);
+      await this.#pc.setLocalDescription(answer);
+      await this.#sendClientDescription(answer);
+    } catch (e) {
+      console.error('Error processing remote description (offer)', e)
+      throw e;
+    }
+  }
+
+  async #onAnswer(answer) {
+    console.debug('Remote description (answer): ', answer);
+    try {
+      await this.#pc.setRemoteDescription(answer);
+    } catch (e) {
+      console.error('Error processing remote description (answer)', e)
+      throw e;
+    }
+  }
+
+  #onIceCandidate(iceCandidate) {
+    console.debug(`Remote ICE Candidate: `, iceCandidate);
+    this.#pc.addIceCandidate(iceCandidate);
+  }
+
+  ConnectDevice(pc) {
+    this.#pc = pc;
+    console.debug('ConnectDevice');
+    // ICE candidates will be generated when we add the offer. Adding it here
+    // instead of in _onOffer because this function is called once per peer
+    // connection, while _onOffer may be called more than once due to
+    // renegotiations.
+    this.#pc.addEventListener('icecandidate', evt => {
+      if (evt.candidate) this.#sendIceCandidate(evt.candidate);
+    });
+    this.#serverConnector.sendToDevice({type: 'request-offer'});
+  }
+
+  async renegotiateConnection() {
+    console.debug('Re-negotiating connection');
+    let offer = await this.#pc.createOffer();
+    console.debug('Local description (offer): ', offer);
+    await this.#pc.setLocalDescription(offer);
+    this.#serverConnector.sendToDevice({type: 'offer', sdp: offer.sdp});
+  }
+}
+
+function createPeerConnection(infra_config) {
+  let pc_config = {iceServers: []};
+  for (const stun of infra_config.ice_servers) {
+    pc_config.iceServers.push({urls: 'stun:' + stun});
+  }
+  let pc = new RTCPeerConnection(pc_config);
+
+  pc.addEventListener('icecandidate', evt => {
+    console.debug('Local ICE Candidate: ', evt.candidate);
+  });
+  pc.addEventListener('iceconnectionstatechange', evt => {
+    console.debug(`ICE State Change: ${pc.iceConnectionState}`);
+  });
+  pc.addEventListener(
+      'connectionstatechange',
+      evt => console.debug(
+          `WebRTC Connection State Change: ${pc.connectionState}`));
+  return pc;
+}
+
+export async function Connect(deviceId, serverConnector) {
+  let requestRet = await serverConnector.requestDevice(deviceId);
+  let deviceInfo = requestRet.deviceInfo;
+  let infraConfig = requestRet.infraConfig;
+  console.debug('Device available:');
+  console.debug(deviceInfo);
+  let pc_config = {iceServers: []};
+  if (infraConfig.ice_servers && infraConfig.ice_servers.length > 0) {
+    for (const server of infraConfig.ice_servers) {
+      pc_config.iceServers.push(server);
+    }
+  }
+  let pc = createPeerConnection(infraConfig);
+
+  let control = new Controller(serverConnector);
+  let deviceConnection = new DeviceConnection(pc, control);
+  deviceConnection.description = deviceInfo;
+
+  return new Promise((resolve, reject) => {
+    pc.addEventListener('connectionstatechange', evt => {
+      let state = pc.connectionState;
+      if (state == 'connected') {
+        resolve(deviceConnection);
+      } else if (state == 'failed') {
+        reject(evt);
+      }
+    });
+    control.ConnectDevice(pc);
+  });
+}
diff --git a/host/frontend/webrtc/client/js/controls.js b/host/frontend/webrtc/client/js/controls.js
new file mode 100644
index 0000000..c9aaac4
--- /dev/null
+++ b/host/frontend/webrtc/client/js/controls.js
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Creates a "toggle control", which is a stylized checkbox with an icon. The
+// onToggleCb callback is called every time the control changes state with the
+// new toggle position (true for ON) and is expected to return a promise of the
+// new toggle position which can resolve to the opposite position of the one
+// received if there was error.
+function createToggleControl(elm, iconName, onToggleCb, initialState = false) {
+  let icon = document.createElement('span');
+  icon.classList.add('toggle-control-icon');
+  icon.classList.add('material-icons-outlined');
+  if (iconName) {
+    icon.appendChild(document.createTextNode(iconName));
+  }
+  elm.appendChild(icon);
+  let toggle = document.createElement('label');
+  toggle.classList.add('toggle-control-switch');
+  let input = document.createElement('input');
+  input.type = 'checkbox';
+  input.checked = !!initialState;
+  input.onchange = e => {
+    let nextPr = onToggleCb(e.target.checked);
+    if (nextPr && 'then' in nextPr) {
+      nextPr.then(checked => {
+        e.target.checked = !!checked;
+      });
+    }
+  };
+  toggle.appendChild(input);
+  let slider = document.createElement('span');
+  slider.classList.add('toggle-control-slider');
+  toggle.appendChild(slider);
+  elm.classList.add('toggle-control');
+  elm.appendChild(toggle);
+  return {
+    // Sets the state of the toggle control. This only affects the
+    // visible state of the control in the UI, it doesn't affect the
+    // state of the underlying resources. It's most useful to make
+    // changes of said resources visible to the user.
+    Set: checked => input.checked = !!checked,
+  };
+}
+
+function createButtonListener(button_id_class, func,
+  deviceConnection, listener) {
+  let buttons = [];
+  let ele = document.getElementById(button_id_class);
+  if (ele != null) {
+    buttons.push(ele);
+  } else {
+    buttons = document.getElementsByClassName(button_id_class);
+  }
+  for (var button of buttons) {
+    if (func != null) {
+      button.onclick = func;
+    }
+    button.addEventListener('mousedown', listener);
+  }
+}
+
+function createInputListener(input_id, func, listener) {
+  input = document.getElementById(input_id);
+  if (func != null) {
+    input.oninput = func;
+  }
+  input.addEventListener('input', listener);
+}
+
+function validateMacAddress(val) {
+  var regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
+  return (regex.test(val));
+}
+
+function validateMacWrapper() {
+  let type = document.getElementById('bluetooth-wizard-type').value;
+  let button = document.getElementById("bluetooth-wizard-device");
+  let macField = document.getElementById('bluetooth-wizard-mac');
+  if (this.id == 'bluetooth-wizard-type') {
+    if (type == "remote_loopback") {
+      button.disabled = false;
+      macField.setCustomValidity('');
+      macField.disabled = true;
+      macField.required = false;
+      macField.placeholder = 'N/A';
+      macField.value = '';
+      return;
+    }
+  }
+  macField.disabled = false;
+  macField.required = true;
+  macField.placeholder = 'Device MAC';
+  if (validateMacAddress($(macField).val())) {
+    button.disabled = false;
+    macField.setCustomValidity('');
+  } else {
+    button.disabled = true;
+    macField.setCustomValidity('MAC address invalid');
+  }
+}
+
+$('[validate-mac]').bind('input', validateMacWrapper);
+$('[validate-mac]').bind('select', validateMacWrapper);
+
+function parseDevice(device) {
+  let id, name, mac;
+  var regex = /([0-9]+):([^@ ]*)(@(([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})))?/;
+  if (regex.test(device)) {
+    let regexMatches = device.match(regex);
+    id = regexMatches[1];
+    name = regexMatches[2];
+    mac = regexMatches[4];
+  }
+  if (mac === undefined) {
+    mac = "";
+  }
+  return [id, name, mac];
+}
+
+function btUpdateAdded(devices) {
+  let deviceArr = devices.split('\r\n');
+  let [id, name, mac] = parseDevice(deviceArr[0]);
+  if (name) {
+    let div = document.getElementById('bluetooth-wizard-confirm').getElementsByClassName('bluetooth-text')[1];
+    div.innerHTML = "";
+    div.innerHTML += "<p>Name: <b>" + id + "</b></p>";
+    div.innerHTML += "<p>Type: <b>" + name + "</b></p>";
+    div.innerHTML += "<p>MAC Addr: <b>" + mac + "</b></p>";
+    return true;
+  }
+  return false;
+}
+
+function parsePhy(phy) {
+  let id = phy.substring(0, phy.indexOf(":"));
+  phy = phy.substring(phy.indexOf(":") + 1);
+  let name = phy.substring(0, phy.indexOf(":"));
+  let devices = phy.substring(phy.indexOf(":") + 1);
+  return [id, name, devices];
+}
+
+function btParsePhys(phys) {
+  if (phys.indexOf("Phys:") < 0) {
+    return null;
+  }
+  let phyDict = {};
+  phys = phys.split('Phys:')[1];
+  let phyArr = phys.split('\r\n');
+  for (var phy of phyArr.slice(1)) {
+    phy = phy.trim();
+    if (phy.length == 0 || phy.indexOf("deleted") >= 0) {
+      continue;
+    }
+    let [id, name, devices] = parsePhy(phy);
+    phyDict[name] = id;
+  }
+  return phyDict;
+}
+
+function btUpdateDeviceList(devices) {
+  let deviceArr = devices.split('\r\n');
+  if (deviceArr[0].indexOf("Devices:") >= 0) {
+    let div = document.getElementById('bluetooth-list').getElementsByClassName('bluetooth-text')[0];
+    div.innerHTML = "";
+    let count = 0;
+    for (var device of deviceArr.slice(1)) {
+      if (device.indexOf("Phys:") >= 0) {
+        break;
+      }
+      count++;
+      if (device.indexOf("deleted") >= 0) {
+        continue;
+      }
+      let [id, name, mac] = parseDevice(device);
+      let innerDiv = '<div><button title="Delete" data-device-id="'
+      innerDiv += id;
+      innerDiv += '" class="bluetooth-list-trash material-icons">delete</button>';
+      innerDiv += name;
+      if (mac) {
+        innerDiv += " | "
+        innerDiv += mac;
+      }
+      innerDiv += '</div>';
+      div.innerHTML += innerDiv;
+    }
+    return count;
+  }
+  return -1;
+}
+
+function createControlPanelButton(
+    command, title, icon_name, listener,
+    parent_id = 'control-panel-default-buttons') {
+  let button = document.createElement('button');
+  document.getElementById(parent_id).appendChild(button);
+  button.title = title;
+  button.dataset.command = command;
+  button.disabled = true;
+  // Capture mousedown/up/out commands instead of click to enable
+  // hold detection. mouseout is used to catch if the user moves the
+  // mouse outside the button while holding down.
+  button.addEventListener('mousedown', listener);
+  button.addEventListener('mouseup', listener);
+  button.addEventListener('mouseout', listener);
+  // Set the button image using Material Design icons.
+  // See http://google.github.io/material-design-icons
+  // and https://material.io/resources/icons
+  button.classList.add('material-icons');
+  button.innerHTML = icon_name;
+  return button;
+}
+
+function positionModal(button_id, modal_id) {
+  const modalButton = document.getElementById(button_id);
+  const modalDiv = document.getElementById(modal_id);
+
+  // Position the modal to the right of the show modal button.
+  modalDiv.style.top = modalButton.offsetTop;
+  modalDiv.style.left = modalButton.offsetWidth + 30;
+}
+
+function createModalButton(button_id, modal_id, close_id, hide_id) {
+  const modalButton = document.getElementById(button_id);
+  const modalDiv = document.getElementById(modal_id);
+  const modalHeader = modalDiv.querySelector('.modal-header');
+  const modalClose = document.getElementById(close_id);
+  const modalDivHide = document.getElementById(hide_id);
+
+  positionModal(button_id, modal_id);
+
+  function showHideModal(show) {
+    if (show) {
+      modalButton.classList.add('modal-button-opened')
+      modalDiv.style.display = 'block';
+    } else {
+      modalButton.classList.remove('modal-button-opened')
+      modalDiv.style.display = 'none';
+    }
+    if (modalDivHide != null) {
+      modalDivHide.style.display = 'none';
+    }
+  }
+  // Allow the show modal button to toggle the modal,
+  modalButton.addEventListener(
+      'click', evt => showHideModal(modalDiv.style.display != 'block'));
+  // but the close button always closes.
+  modalClose.addEventListener('click', evt => showHideModal(false));
+
+  // Allow the modal to be dragged by the header.
+  let modalOffsets = {
+    midDrag: false,
+    mouseDownOffsetX: null,
+    mouseDownOffsetY: null,
+  };
+  modalHeader.addEventListener('mousedown', evt => {
+    modalOffsets.midDrag = true;
+    // Store the offset of the mouse location from the
+    // modal's current location.
+    modalOffsets.mouseDownOffsetX = parseInt(modalDiv.style.left) - evt.clientX;
+    modalOffsets.mouseDownOffsetY = parseInt(modalDiv.style.top) - evt.clientY;
+  });
+  modalHeader.addEventListener('mousemove', evt => {
+    let offsets = modalOffsets;
+    if (offsets.midDrag) {
+      // Move the modal to the mouse location plus the
+      // offset calculated on the initial mouse-down.
+      modalDiv.style.left = evt.clientX + offsets.mouseDownOffsetX;
+      modalDiv.style.top = evt.clientY + offsets.mouseDownOffsetY;
+    }
+  });
+  document.addEventListener('mouseup', evt => {
+    modalOffsets.midDrag = false;
+  });
+}
+
+function cmdConsole(consoleViewName, consoleInputName) {
+  let consoleView = document.getElementById(consoleViewName);
+
+  let addString =
+      function(str) {
+    consoleView.value += str;
+    consoleView.scrollTop = consoleView.scrollHeight;
+  }
+
+  let addLine =
+      function(line) {
+    addString(line + '\r\n');
+  }
+
+  let commandCallbacks = [];
+
+  let addCommandListener =
+      function(f) {
+    commandCallbacks.push(f);
+  }
+
+  let onCommand =
+      function(cmd) {
+    cmd = cmd.trim();
+
+    if (cmd.length == 0) return;
+
+    commandCallbacks.forEach(f => {
+      f(cmd);
+    })
+  }
+
+  addCommandListener(cmd => addLine('>> ' + cmd));
+
+  let consoleInput = document.getElementById(consoleInputName);
+
+  consoleInput.addEventListener('keydown', e => {
+    if ((e.key && e.key == 'Enter') || e.keyCode == 13) {
+      let command = e.target.value;
+
+      e.target.value = '';
+
+      onCommand(command);
+    }
+  });
+
+  return {
+    consoleView: consoleView,
+    consoleInput: consoleInput,
+    addLine: addLine,
+    addString: addString,
+    addCommandListener: addCommandListener,
+  };
+}
diff --git a/host/frontend/webrtc_operator/assets/js/rootcanal.js b/host/frontend/webrtc/client/js/rootcanal.js
similarity index 84%
rename from host/frontend/webrtc_operator/assets/js/rootcanal.js
rename to host/frontend/webrtc/client/js/rootcanal.js
index 4443bcd..e3783c7 100644
--- a/host/frontend/webrtc_operator/assets/js/rootcanal.js
+++ b/host/frontend/webrtc/client/js/rootcanal.js
@@ -19,12 +19,12 @@
 function rootCanalCalculateMessageSize(name, args) {
   let result = 0;
 
-  result += 1 + name.length; // length of name + it's data
-  result += 1; // count of args
+  result += 1 + name.length;  // length of name + it's data
+  result += 1;                // count of args
 
-  for(let i = 0; i < args.length; i++) {
-    result += 1; // length of args[i]
-    result += args[i].length; // data of args[i]
+  for (let i = 0; i < args.length; i++) {
+    result += 1;               // length of args[i]
+    result += args[i].length;  // data of args[i]
   }
 
   return result;
@@ -59,7 +59,7 @@
   pos = rootCanalAddString(array, pos, command);
   pos = rootCanalAddU8(array, pos, args.length);
 
-  for(let i = 0; i < args.length; i++) {
+  for (let i = 0; i < args.length; i++) {
     pos = rootCanalAddString(array, pos, args[i]);
   }
 
@@ -71,4 +71,4 @@
   let message = array.slice(1);
 
   return utf8Decoder.decode(message);
-}
\ No newline at end of file
+}
diff --git a/host/frontend/webrtc/client/style.css b/host/frontend/webrtc/client/style.css
new file mode 100644
index 0000000..93aaa05
--- /dev/null
+++ b/host/frontend/webrtc/client/style.css
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+body {
+  background-color:black;
+  margin: 0;
+  touch-action: none;
+  overscroll-behavior: none;
+}
+
+#device-connection {
+  display: none;
+  max-height: 100vh;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+#loader {
+  border-left: 12px solid #4285F4;
+  border-top: 12px solid #34A853;
+  border-right: 12px solid #FBBC05;
+  border-bottom: 12px solid #EA4335;
+  border-radius: 50%;
+  width: 70px;
+  height: 70px;
+  animation: spin 1.2s linear infinite;
+  margin: 100px;
+}
+
+/* Top header row. */
+
+#header {
+  height: 64px;
+  /* Items inside this use a row Flexbox.*/
+  display: flex;
+  align-items: center;
+}
+
+#camera-control {
+  display: none !important;
+}
+#record-video-control {
+  display: none !important;
+}
+
+#app-controls {
+  margin-left: 10px;
+}
+#app-controls > div {
+  display: inline-block;
+  position: relative;
+  margin-right: 6px;
+}
+#device-audio {
+  height: 44px;
+}
+
+#error-message-div {
+  flex-grow: 1;
+}
+#error-message {
+  color: white;
+  font-family: 'Open Sans', sans-serif;
+  padding: 10px;
+  margin: 10px;
+  border-radius: 10px;
+}
+#error-message .close-btn {
+  float: right;
+  cursor: pointer;
+}
+#error-message.hidden {
+  display: none;
+}
+#error-message.warning {
+  /* dark red */
+  background-color: #927836;
+}
+#error-message.error {
+  /* dark red */
+  background-color: #900000;
+}
+#status-div {
+  flex-grow: 1;
+}
+#status-message {
+  color: white;
+  font-family: 'Open Sans', sans-serif;
+  padding: 10px;
+  margin: 10px;
+}
+#status-message.connecting {
+  /* dark yellow */
+  background-color: #927836;
+}
+#status-message.error {
+  /* dark red */
+  background-color: #900000;
+}
+#status-message.connected {
+  /* dark green */
+  background-color: #007000;
+}
+
+/* Control panel buttons and device screen(s). */
+
+#controls-and-displays {
+  height: calc(100% - 84px);
+
+  /* Items inside this use a row Flexbox.*/
+  display: flex;
+}
+
+#controls-and-displays > div {
+  margin-left: 5px;
+  margin-right: 5px;
+}
+
+.modal {
+  /* Start out hidden, and use absolute positioning. */
+  display: none;
+  position: absolute;
+
+  border-radius: 10px;
+  padding: 20px;
+  padding-top: 1px;
+
+  background-color: #5f6368ea; /* Semi-transparent Google grey 500 */
+  color: white;
+  font-family: 'Open Sans', sans-serif;
+}
+.modal-header {
+  cursor: move;
+  /* Items inside this use a row Flexbox.*/
+  display: flex;
+  justify-content: space-between;
+}
+.modal-close {
+  color: white;
+  border: none;
+  outline: none;
+  background-color: transparent;
+}
+.modal-button, .modal-button-highlight {
+  background:    #e8eaed; /* Google grey 200 */
+  border-radius: 10px;
+  box-shadow:    1px 1px #444444;
+  padding:       10px 20px;
+  color:         #000000;
+  display:       inline-block;
+  font:          normal bold 14px/1 "Open Sans", sans-serif;
+  text-align:    center;
+}
+#bluetooth-wizard-mac:valid {
+  border: 2px solid black;
+}
+#bluetooth-wizard-mac:invalid {
+  border: 2px solid red;
+}
+#bluetooth-wizard-mac:invalid + span::before {
+  font-weight: bold;
+  content: 'X';
+  color: red;
+}
+#bluetooth-wizard-mac:valid + span::before {
+  font-weight: bold;
+  content: 'OK';
+  color: green;
+}
+.modal-button {
+  background:    #e8eaed; /* Google grey 200 */
+}
+.modal-button-highlight {
+  background:    #f4cccc;
+}
+#device-details-modal span {
+  white-space: pre;
+}
+#bluetooth-console-input {
+  width: 100%;
+}
+#bluetooth-console-cmd-label {
+  color: white;
+}
+.bluetooth-text, .bluetooth-text-bold, .bluetooth-text-field input {
+  font: normal 18px/1 "Open Sans", sans-serif;
+}
+.bluetooth-text, .bluetooth-text-bold {
+  color: white;
+}
+.bluetooth-text-bold {
+  font: bold;
+}
+.bluetooth-button {
+  text-align: center;
+}
+.bluetooth-drop-down select {
+  font: normal 18px/1 "Open Sans", sans-serif;
+  color: black;
+  width: 500px;
+  margin: 5px;
+  rows: 10;
+  columns: 60;
+}
+.bluetooth-text-field input {
+  color: black;
+  width: 500px;
+  margin: 5px;
+  rows: 10;
+  columns: 60;
+}
+.bluetooth-list-trash {
+  background:    #00000000;
+  border:        0px;
+  color:         #ffffff;
+}
+
+.control-panel-column {
+  width: 50px;
+  /* Items inside this use a column Flexbox.*/
+  display: flex;
+  flex-direction: column;
+}
+#control-panel-custom-buttons {
+  display: none;
+  /* Give the custom buttons column a blue background. */
+  background-color: #1c4587ff;
+  height: fit-content;
+  border-radius: 10px;
+}
+
+.control-panel-column button {
+  margin: 0px 0px 5px 0px;
+  height: 50px;
+  font-size: 32px;
+
+  color: #e8eaed; /* Google grey 200 */
+  border: none;
+  outline: none;
+  background-color: transparent;
+}
+.control-panel-column button:disabled {
+  color: #9aa0a6; /* Google grey 500 */
+}
+.control-panel-column button.modal-button-opened {
+  border-radius: 10px;
+  background-color: #5f6368; /* Google grey 700 */
+}
+
+#device-displays {
+  /* Take up the remaining width of the window.*/
+  flex-grow: 1;
+  /* Don't grow taller than the window.*/
+  max-height: 100vh;
+  /* Allows child elements to be positioned relative to this element. */
+  position: relative;
+}
+
+/*
+ * Container <div> used to wrap each display's <video> element which is used for
+ * maintaining each display's width and height while the display is potentially
+ * rotating.
+ */
+.device-display {
+  /* Prevents #device-displays from using this element when computing flex size. */
+  position: absolute;
+}
+
+/* Container <div> to show info about the individual display. */
+.device-display-info {
+  color: white;
+  /* dark green */
+  background-color: #007000;
+  font-family: 'Open Sans', sans-serif;
+  text-indent: 0px;
+  border-radius: 10px;
+  padding: 10px;
+  margin-bottom: 10px;
+}
+
+/* The actual <video> element for each display. */
+.device-display-video {
+  position: absolute;
+  left:  0px;
+  touch-action: none;
+  object-fit: cover;
+}
diff --git a/host/frontend/webrtc/client_server.cpp b/host/frontend/webrtc/client_server.cpp
new file mode 100644
index 0000000..cfd4bb7
--- /dev/null
+++ b/host/frontend/webrtc/client_server.cpp
@@ -0,0 +1,101 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/frontend/webrtc/client_server.h"
+#include <android-base/logging.h>
+
+namespace cuttlefish {
+struct ClientFilesServer::Config {
+  Config(const std::string& dir)
+      : dir_(dir),
+        mount_({
+            .mount_next = nullptr,  /* linked-list "next" */
+            .mountpoint = "/",      /* mountpoint URL */
+            .origin = dir_.c_str(), /* serve from dir */
+            .def = "client.html",   /* default filename */
+            .protocol = nullptr,
+            .cgienv = nullptr,
+            .extra_mimetypes = nullptr,
+            .interpret = nullptr,
+            .cgi_timeout = 0,
+            .cache_max_age = 0,
+            .auth_mask = 0,
+            .cache_reusable = 0,
+            .cache_revalidate = 0,
+            .cache_intermediaries = 0,
+            .origin_protocol = LWSMPRO_FILE, /* files in a dir */
+            .mountpoint_len = 1,             /* char count */
+            .basic_auth_login_file = nullptr,
+        }) {
+    memset(&info_, 0, sizeof info_);
+    info_.port = 0;             // let the kernel select an available port
+    info_.iface = "127.0.0.1";  // listen only on localhost
+    info_.mounts = &mount_;
+  }
+
+  std::string dir_;
+  lws_http_mount mount_;
+  lws_context_creation_info info_;
+};
+
+ClientFilesServer::ClientFilesServer(std::unique_ptr<Config> config,
+                                     lws_context* context)
+    : config_(std::move(config)),
+      context_(context),
+      running_(true),
+      server_thread_([this]() { Serve(); }) {}
+
+ClientFilesServer::~ClientFilesServer() {
+  if (running_) {
+    running_ = false;
+    server_thread_.join();
+  }
+  if (context_) {
+    // Release the port and other resources
+    lws_context_destroy(context_);
+  }
+}
+
+std::unique_ptr<ClientFilesServer> ClientFilesServer::New(
+    const std::string& dir) {
+  std::unique_ptr<Config> conf(new Config(dir));
+  if (!conf) {
+    return nullptr;
+  }
+
+  auto ctx = lws_create_context(&conf->info_);
+  if (!ctx) {
+    LOG(ERROR) << "Failed to create lws context";
+    return nullptr;
+  }
+  return std::unique_ptr<ClientFilesServer>(
+      new ClientFilesServer(std::move(conf), ctx));
+}
+
+int ClientFilesServer::port() const {
+  // Get the port for the first (and only) vhost.
+  return lws_get_vhost_listen_port(lws_get_vhost_by_name(context_, "default"));
+}
+
+void ClientFilesServer::Serve() {
+  while (running_) {
+    if (lws_service(context_, 0) < 0) {
+      LOG(ERROR) << "Error serving client files";
+      return;
+    }
+  }
+}
+}  // namespace cuttlefish
+
diff --git a/host/frontend/webrtc/client_server.h b/host/frontend/webrtc/client_server.h
new file mode 100644
index 0000000..8d6376e
--- /dev/null
+++ b/host/frontend/webrtc/client_server.h
@@ -0,0 +1,47 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include <thread>
+
+#include <libwebsockets.h>
+
+namespace cuttlefish {
+// Utility class to serve the client files in a thread
+class ClientFilesServer {
+ public:
+  ~ClientFilesServer();
+
+  static std::unique_ptr<ClientFilesServer> New(const std::string& dir);
+
+  int port() const;
+
+ private:
+  struct Config;
+
+  ClientFilesServer(std::unique_ptr<Config> config, lws_context* context);
+
+  void Serve();
+
+  std::unique_ptr<Config> config_;
+  lws_context* context_;
+  std::atomic<bool> running_;
+  std::thread server_thread_;
+};
+}  // namespace cuttlefish
diff --git a/host/frontend/webrtc/connection_observer.cpp b/host/frontend/webrtc/connection_observer.cpp
index 0975faa..ec632b6 100644
--- a/host/frontend/webrtc/connection_observer.cpp
+++ b/host/frontend/webrtc/connection_observer.cpp
@@ -96,26 +96,25 @@
  * i.e. when it is not in the confirmation UI mode (or TEE),
  * the control flow will fall back to this ConnectionObserverForAndroid
  */
-class ConnectionObserverForAndroid
+class ConnectionObserverImpl
     : public cuttlefish::webrtc_streaming::ConnectionObserver {
  public:
-  ConnectionObserverForAndroid(
+  ConnectionObserverImpl(
       cuttlefish::InputSockets &input_sockets,
       cuttlefish::KernelLogEventsHandler *kernel_log_events_handler,
       std::map<std::string, cuttlefish::SharedFD>
           commands_to_custom_action_servers,
       std::weak_ptr<DisplayHandler> display_handler,
-      CameraController *camera_controller)
+      CameraController *camera_controller,
+      cuttlefish::confui::HostVirtualInput &confui_input)
       : input_sockets_(input_sockets),
         kernel_log_events_handler_(kernel_log_events_handler),
         commands_to_custom_action_servers_(commands_to_custom_action_servers),
         weak_display_handler_(display_handler),
-        camera_controller_(camera_controller) {}
-  virtual ~ConnectionObserverForAndroid() {
+        camera_controller_(camera_controller),
+        confui_input_(confui_input) {}
+  virtual ~ConnectionObserverImpl() {
     auto display_handler = weak_display_handler_.lock();
-    if (display_handler) {
-      display_handler->DecClientCount();
-    }
     if (kernel_log_subscription_id_ != -1) {
       kernel_log_events_handler_->Unsubscribe(kernel_log_subscription_id_);
     }
@@ -125,7 +124,6 @@
                    /*ctrl_msg_sender*/) override {
     auto display_handler = weak_display_handler_.lock();
     if (display_handler) {
-      display_handler->IncClientCount();
       std::thread th([this]() {
         // The encoder in libwebrtc won't drop 5 consecutive frames due to frame
         // size, so we make sure at least 5 frames are sent every time a client
@@ -142,69 +140,89 @@
       });
       th.detach();
     }
+  }
+
+  void OnTouchEvent(const std::string &display_label, int x, int y,
+                    bool down) override {
+    if (confui_input_.IsConfUiActive()) {
+      ConfUiLog(DEBUG) << "delivering a touch event in confirmation UI mode";
+      confui_input_.TouchEvent(x, y, down);
+      return;
+    }
+    auto buffer = GetEventBuffer();
+    if (!buffer) {
+      LOG(ERROR) << "Failed to allocate event buffer";
+      return;
+    }
+    buffer->AddEvent(EV_ABS, ABS_X, x);
+    buffer->AddEvent(EV_ABS, ABS_Y, y);
+    buffer->AddEvent(EV_KEY, BTN_TOUCH, down);
+    buffer->AddEvent(EV_SYN, SYN_REPORT, 0);
+    cuttlefish::WriteAll(input_sockets_.GetTouchClientByLabel(display_label),
+                         reinterpret_cast<const char *>(buffer->data()),
+                         buffer->size());
+  }
+
+  void OnMultiTouchEvent(const std::string &display_label, Json::Value id,
+                         Json::Value slot, Json::Value x, Json::Value y,
+                         bool down, int size) {
+    auto buffer = GetEventBuffer();
+    if (!buffer) {
+      LOG(ERROR) << "Failed to allocate event buffer";
+      return;
     }
 
-    void OnTouchEvent(const std::string &display_label, int x, int y,
-                      bool down) override {
-      auto buffer = GetEventBuffer();
-      if (!buffer) {
-        LOG(ERROR) << "Failed to allocate event buffer";
-        return;
-      }
-      buffer->AddEvent(EV_ABS, ABS_X, x);
-      buffer->AddEvent(EV_ABS, ABS_Y, y);
-      buffer->AddEvent(EV_KEY, BTN_TOUCH, down);
-      buffer->AddEvent(EV_SYN, SYN_REPORT, 0);
-      cuttlefish::WriteAll(input_sockets_.GetTouchClientByLabel(display_label),
-                           reinterpret_cast<const char *>(buffer->data()),
-                           buffer->size());
-    }
+    for (int i = 0; i < size; i++) {
+      auto this_slot = slot[i].asInt();
+      auto this_id = id[i].asInt();
+      auto this_x = x[i].asInt();
+      auto this_y = y[i].asInt();
 
-    void OnMultiTouchEvent(const std::string &display_label, Json::Value id,
-                           Json::Value slot, Json::Value x, Json::Value y,
-                           bool down, int size) override {
-      auto buffer = GetEventBuffer();
-      if (!buffer) {
-        LOG(ERROR) << "Failed to allocate event buffer";
-        return;
-      }
-
-      for (int i = 0; i < size; i++) {
-        auto this_slot = slot[i].asInt();
-        auto this_id = id[i].asInt();
-        auto this_x = x[i].asInt();
-        auto this_y = y[i].asInt();
-        buffer->AddEvent(EV_ABS, ABS_MT_SLOT, this_slot);
+      if (confui_input_.IsConfUiActive()) {
         if (down) {
-          bool is_new = active_touch_slots_.insert(this_slot).second;
-          if (is_new) {
-            buffer->AddEvent(EV_ABS, ABS_MT_TRACKING_ID, this_id);
-            if (active_touch_slots_.size() == 1) {
-              buffer->AddEvent(EV_KEY, BTN_TOUCH, 1);
-            }
-          }
-          buffer->AddEvent(EV_ABS, ABS_MT_POSITION_X, this_x);
-          buffer->AddEvent(EV_ABS, ABS_MT_POSITION_Y, this_y);
-          // send ABS_X and ABS_Y for single-touch compatibility
-          buffer->AddEvent(EV_ABS, ABS_X, this_x);
-          buffer->AddEvent(EV_ABS, ABS_Y, this_y);
-        } else {
-          // released touch
+          ConfUiLog(DEBUG) << "Delivering event (" << x << ", " << y
+                           << ") to conf ui";
+        }
+        confui_input_.TouchEvent(this_x, this_y, down);
+        continue;
+      }
+
+      buffer->AddEvent(EV_ABS, ABS_MT_SLOT, this_slot);
+      if (down) {
+        bool is_new = active_touch_slots_.insert(this_slot).second;
+        if (is_new) {
           buffer->AddEvent(EV_ABS, ABS_MT_TRACKING_ID, this_id);
-          active_touch_slots_.erase(this_slot);
-          if (active_touch_slots_.empty()) {
-            buffer->AddEvent(EV_KEY, BTN_TOUCH, 0);
+          if (active_touch_slots_.size() == 1) {
+            buffer->AddEvent(EV_KEY, BTN_TOUCH, 1);
           }
         }
+        buffer->AddEvent(EV_ABS, ABS_MT_POSITION_X, this_x);
+        buffer->AddEvent(EV_ABS, ABS_MT_POSITION_Y, this_y);
+        // send ABS_X and ABS_Y for single-touch compatibility
+        buffer->AddEvent(EV_ABS, ABS_X, this_x);
+        buffer->AddEvent(EV_ABS, ABS_Y, this_y);
+      } else {
+        // released touch
+        buffer->AddEvent(EV_ABS, ABS_MT_TRACKING_ID, this_id);
+        active_touch_slots_.erase(this_slot);
+        if (active_touch_slots_.empty()) {
+          buffer->AddEvent(EV_KEY, BTN_TOUCH, 0);
+        }
       }
-
-      buffer->AddEvent(EV_SYN, SYN_REPORT, 0);
-      cuttlefish::WriteAll(input_sockets_.GetTouchClientByLabel(display_label),
-                           reinterpret_cast<const char *>(buffer->data()),
-                           buffer->size());
     }
 
+    buffer->AddEvent(EV_SYN, SYN_REPORT, 0);
+    cuttlefish::WriteAll(input_sockets_.GetTouchClientByLabel(display_label),
+                         reinterpret_cast<const char *>(buffer->data()),
+                         buffer->size());
+  }
+
   void OnKeyboardEvent(uint16_t code, bool down) override {
+    if (confui_input_.IsConfUiActive()) {
+      ConfUiLog(DEBUG) << "keyboard event ignored in confirmation UI mode";
+      return;
+    }
+
     auto buffer = GetEventBuffer();
     if (!buffer) {
       LOG(ERROR) << "Failed to allocate event buffer";
@@ -303,8 +321,6 @@
       OnKeyboardEvent(KEY_HOMEPAGE, button_state == "down");
     } else if (command == "menu") {
       OnKeyboardEvent(KEY_MENU, button_state == "down");
-    } else if (command == "volumemute") {
-      OnKeyboardEvent(KEY_MUTE, button_state == "down");
     } else if (command == "volumedown") {
       OnKeyboardEvent(KEY_VOLUMEDOWN, button_state == "down");
     } else if (command == "volumeup") {
@@ -327,11 +343,10 @@
   void OnBluetoothChannelOpen(std::function<bool(const uint8_t *, size_t)>
                                   bluetooth_message_sender) override {
     LOG(VERBOSE) << "Bluetooth channel open";
+    auto config = cuttlefish::CuttlefishConfig::Get();
+    CHECK(config) << "Failed to get config";
     bluetooth_handler_.reset(new cuttlefish::webrtc_streaming::BluetoothHandler(
-        cuttlefish::CuttlefishConfig::Get()
-            ->ForDefaultInstance()
-            .rootcanal_test_port(),
-        bluetooth_message_sender));
+        config->rootcanal_test_port(), bluetooth_message_sender));
   }
 
   void OnBluetoothMessage(const uint8_t *msg, size_t size) override {
@@ -355,107 +370,6 @@
   std::weak_ptr<DisplayHandler> weak_display_handler_;
   std::set<int32_t> active_touch_slots_;
   cuttlefish::CameraController *camera_controller_;
-};
-
-class ConnectionObserverDemuxer
-    : public cuttlefish::webrtc_streaming::ConnectionObserver {
- public:
-  ConnectionObserverDemuxer(
-      /* params for the base class */
-      cuttlefish::InputSockets &input_sockets,
-      cuttlefish::KernelLogEventsHandler *kernel_log_events_handler,
-      std::map<std::string, cuttlefish::SharedFD>
-          commands_to_custom_action_servers,
-      std::weak_ptr<DisplayHandler> display_handler,
-      CameraController *camera_controller,
-      /* params for this class */
-      cuttlefish::confui::HostVirtualInput &confui_input)
-      : android_input_(input_sockets, kernel_log_events_handler,
-                       commands_to_custom_action_servers, display_handler,
-                       camera_controller),
-        confui_input_{confui_input} {}
-  virtual ~ConnectionObserverDemuxer() = default;
-
-  void OnConnected(std::function<void(const uint8_t *, size_t, bool)>
-                       ctrl_msg_sender) override {
-    android_input_.OnConnected(ctrl_msg_sender);
-  }
-
-  void OnTouchEvent(const std::string &label, int x, int y,
-                    bool down) override {
-    if (confui_input_.IsConfUiActive()) {
-      ConfUiLog(DEBUG) << "touch event ignored in confirmation UI mode";
-      return;
-    }
-    android_input_.OnTouchEvent(label, x, y, down);
-  }
-
-  void OnMultiTouchEvent(const std::string &label, Json::Value id,
-                         Json::Value slot, Json::Value x, Json::Value y,
-                         bool down, int size) override {
-    if (confui_input_.IsConfUiActive()) {
-      ConfUiLog(DEBUG) << "multi-touch event ignored in confirmation UI mode";
-      return;
-    }
-    android_input_.OnMultiTouchEvent(label, id, slot, x, y, down, size);
-  }
-
-  void OnKeyboardEvent(uint16_t code, bool down) override {
-    if (confui_input_.IsConfUiActive()) {
-      switch (code) {
-        case KEY_POWER:
-          confui_input_.PressConfirmButton(down);
-          break;
-        case KEY_MENU:
-          confui_input_.PressCancelButton(down);
-          break;
-        default:
-          ConfUiLog(DEBUG) << "key" << code
-                           << "is ignored in confirmation UI mode";
-          break;
-      }
-      return;
-    }
-    android_input_.OnKeyboardEvent(code, down);
-  }
-
-  void OnSwitchEvent(uint16_t code, bool state) override {
-    android_input_.OnSwitchEvent(code, state);
-  }
-
-  void OnAdbChannelOpen(std::function<bool(const uint8_t *, size_t)>
-                            adb_message_sender) override {
-    android_input_.OnAdbChannelOpen(adb_message_sender);
-  }
-
-  void OnAdbMessage(const uint8_t *msg, size_t size) override {
-    android_input_.OnAdbMessage(msg, size);
-  }
-
-  void OnControlChannelOpen(
-      std::function<bool(const Json::Value)> control_message_sender) override {
-    android_input_.OnControlChannelOpen(control_message_sender);
-  }
-
-  void OnControlMessage(const uint8_t *msg, size_t size) override {
-    android_input_.OnControlMessage(msg, size);
-  }
-
-  void OnBluetoothChannelOpen(std::function<bool(const uint8_t *, size_t)>
-                                  bluetooth_message_sender) override {
-    android_input_.OnBluetoothChannelOpen(bluetooth_message_sender);
-  }
-
-  void OnBluetoothMessage(const uint8_t *msg, size_t size) override {
-    android_input_.OnBluetoothMessage(msg, size);
-  }
-
-  void OnCameraData(const std::vector<char> &data) override {
-    android_input_.OnCameraData(data);
-  }
-
- private:
-  ConnectionObserverForAndroid android_input_;
   cuttlefish::confui::HostVirtualInput &confui_input_;
 };
 
@@ -470,10 +384,10 @@
 std::shared_ptr<cuttlefish::webrtc_streaming::ConnectionObserver>
 CfConnectionObserverFactory::CreateObserver() {
   return std::shared_ptr<cuttlefish::webrtc_streaming::ConnectionObserver>(
-      new ConnectionObserverDemuxer(input_sockets_, kernel_log_events_handler_,
-                                    commands_to_custom_action_servers_,
-                                    weak_display_handler_, camera_controller_,
-                                    confui_input_));
+      new ConnectionObserverImpl(input_sockets_, kernel_log_events_handler_,
+                                 commands_to_custom_action_servers_,
+                                 weak_display_handler_, camera_controller_,
+                                 confui_input_));
 }
 
 void CfConnectionObserverFactory::AddCustomActionServer(
diff --git a/host/frontend/webrtc/display_handler.cpp b/host/frontend/webrtc/display_handler.cpp
index 334c35f..3de1516 100644
--- a/host/frontend/webrtc/display_handler.cpp
+++ b/host/frontend/webrtc/display_handler.cpp
@@ -91,19 +91,4 @@
     display_sinks_[buffer_display]->OnFrame(buffer, time_stamp);
   }
 }
-
-void DisplayHandler::IncClientCount() {
-  client_count_++;
-  if (client_count_ == 1) {
-    screen_connector_.ReportClientsConnected(true);
-  }
-}
-
-void DisplayHandler::DecClientCount() {
-  client_count_--;
-  if (client_count_ == 0) {
-    screen_connector_.ReportClientsConnected(false);
-  }
-}
-
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc/display_handler.h b/host/frontend/webrtc/display_handler.h
index 7703b08..1411984 100644
--- a/host/frontend/webrtc/display_handler.h
+++ b/host/frontend/webrtc/display_handler.h
@@ -60,9 +60,6 @@
   [[noreturn]] void Loop();
   void SendLastFrame();
 
-  void IncClientCount();
-  void DecClientCount();
-
  private:
   GenerateProcessedFrameCallback GetScreenConnectorCallback();
   std::vector<std::shared_ptr<webrtc_streaming::VideoSink>> display_sinks_;
@@ -71,6 +68,5 @@
   std::uint32_t last_buffer_display_ = 0;
   std::mutex last_buffer_mutex_;
   std::mutex next_frame_mutex_;
-  int client_count_ = 0;
 };
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc/lib/camera_streamer.cpp b/host/frontend/webrtc/lib/camera_streamer.cpp
index 7634e28..c9c7cf0 100644
--- a/host/frontend/webrtc/lib/camera_streamer.cpp
+++ b/host/frontend/webrtc/lib/camera_streamer.cpp
@@ -23,7 +23,7 @@
 namespace webrtc_streaming {
 
 CameraStreamer::CameraStreamer(unsigned int port, unsigned int cid)
-    : cid_(cid), port_(port) {}
+    : cid_(cid), port_(port), camera_session_active_(false) {}
 
 CameraStreamer::~CameraStreamer() { Disconnect(); }
 
@@ -47,9 +47,10 @@
     LOG(INFO) << "Connected!";
   }
   auto resolution = resolution_.load();
-  if (resolution.height <= 0 || resolution.width <= 0) {
-    // We don't have a valid resolution that is necessary for
-    // potential frame scaling
+  if (resolution.height <= 0 || resolution.width <= 0 ||
+      !camera_session_active_.load()) {
+    // Nobody is receiving frames or we don't have a valid resolution that is
+    // necessary for potential frame scaling
     return;
   }
   auto frame = client_frame.video_frame_buffer()->ToI420().get();
@@ -142,7 +143,16 @@
   }
   reader_thread_ = std::thread([this] {
     while (cvd_connection_.IsConnected()) {
+      static constexpr auto kEventKey = "event";
+      static constexpr auto kMessageStart =
+          "VIRTUAL_DEVICE_START_CAMERA_SESSION";
+      static constexpr auto kMessageStop = "VIRTUAL_DEVICE_STOP_CAMERA_SESSION";
       auto json_value = cvd_connection_.ReadJsonMessage();
+      if (json_value[kEventKey] == kMessageStart) {
+        camera_session_active_ = true;
+      } else if (json_value[kEventKey] == kMessageStop) {
+        camera_session_active_ = false;
+      }
       if (!json_value.empty()) {
         SendMessage(json_value);
       }
diff --git a/host/frontend/webrtc/lib/camera_streamer.h b/host/frontend/webrtc/lib/camera_streamer.h
index ceab2e6..3afed62 100644
--- a/host/frontend/webrtc/lib/camera_streamer.h
+++ b/host/frontend/webrtc/lib/camera_streamer.h
@@ -67,6 +67,7 @@
   unsigned int cid_;
   unsigned int port_;
   std::thread reader_thread_;
+  std::atomic<bool> camera_session_active_;
 };
 
 }  // namespace webrtc_streaming
diff --git a/host/frontend/webrtc/lib/client_handler.cpp b/host/frontend/webrtc/lib/client_handler.cpp
index 4893f8d..9be1ca0 100644
--- a/host/frontend/webrtc/lib/client_handler.cpp
+++ b/host/frontend/webrtc/lib/client_handler.cpp
@@ -103,6 +103,39 @@
 
 }  // namespace
 
+// Video streams initiating in the client may be added and removed at unexpected
+// times, causing the webrtc objects to be destroyed and created every time.
+// This class hides away that complexity and allows to set up sinks only once.
+class ClientVideoTrackImpl : public ClientVideoTrackInterface {
+ public:
+  void AddOrUpdateSink(rtc::VideoSinkInterface<webrtc::VideoFrame> *sink,
+                       const rtc::VideoSinkWants &wants) override {
+    sink_ = sink;
+    wants_ = wants;
+    if (video_track_) {
+      video_track_->AddOrUpdateSink(sink, wants);
+    }
+  }
+
+  void SetVideoTrack(webrtc::VideoTrackInterface *track) {
+    video_track_ = track;
+    if (sink_) {
+      video_track_->AddOrUpdateSink(sink_, wants_);
+    }
+  }
+
+  void UnsetVideoTrack(webrtc::VideoTrackInterface *track) {
+    if (track == video_track_) {
+      video_track_ = nullptr;
+    }
+  }
+
+ private:
+  webrtc::VideoTrackInterface* video_track_;
+  rtc::VideoSinkInterface<webrtc::VideoFrame> *sink_ = nullptr;
+  rtc::VideoSinkWants wants_ = {};
+};
+
 class InputChannelHandler : public webrtc::DataChannelObserver {
  public:
   InputChannelHandler(
@@ -439,7 +472,8 @@
     : client_id_(client_id),
       observer_(observer),
       send_to_client_(send_to_client_cb),
-      on_connection_changed_cb_(on_connection_changed_cb) {}
+      on_connection_changed_cb_(on_connection_changed_cb),
+      camera_track_(new ClientVideoTrackImpl()) {}
 
 ClientHandler::~ClientHandler() {
   for (auto &data_channel : data_channels_) {
@@ -499,15 +533,8 @@
   return true;
 }
 
-webrtc::VideoTrackInterface *ClientHandler::GetCameraStream() const {
-  for (const auto &tranceiver : peer_connection_->GetTransceivers()) {
-    auto track = tranceiver->receiver()->track();
-    if (track &&
-        track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) {
-      return static_cast<webrtc::VideoTrackInterface *>(track.get());
-    }
-  }
-  return nullptr;
+ClientVideoTrackInterface* ClientHandler::GetCameraStream() {
+  return camera_track_.get();
 }
 
 void ClientHandler::LogAndReplyError(const std::string &error_msg) const {
@@ -518,10 +545,24 @@
   send_to_client_(reply);
 }
 
+void ClientHandler::AddPendingIceCandidates() {
+  // Add any ice candidates that arrived before the remote description
+  for (auto& candidate: pending_ice_candidates_) {
+    peer_connection_->AddIceCandidate(std::move(candidate),
+                                      [this](webrtc::RTCError error) {
+                                        if (!error.ok()) {
+                                          LogAndReplyError(error.message());
+                                        }
+                                      });
+  }
+  pending_ice_candidates_.clear();
+}
+
 void ClientHandler::OnCreateSDPSuccess(
     webrtc::SessionDescriptionInterface *desc) {
   std::string offer_str;
   desc->ToString(&offer_str);
+  std::string sdp_type = desc->type();
   peer_connection_->SetLocalDescription(
       // The peer connection wraps this raw pointer with a scoped_refptr, so
       // it's guaranteed to be deleted at some point
@@ -533,7 +574,7 @@
   desc = nullptr;
 
   Json::Value reply;
-  reply["type"] = "offer";
+  reply["type"] = sdp_type;
   reply["sdp"] = offer_str;
 
   state_ = State::kAwaitingAnswer;
@@ -582,6 +623,42 @@
         webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
     // The created offer wil be sent to the client on
     // OnSuccess(webrtc::SessionDescriptionInterface* desc)
+  } else if (type == "offer") {
+    auto result = ValidationResult::ValidateJsonObject(
+        message, type, {{"sdp", Json::ValueType::stringValue}});
+    if (!result.ok()) {
+      LogAndReplyError(result.error());
+      return;
+    }
+    auto remote_desc_str = message["sdp"].asString();
+    auto remote_desc = webrtc::CreateSessionDescription(
+        webrtc::SdpType::kOffer, remote_desc_str, nullptr /*error*/);
+    if (!remote_desc) {
+      LogAndReplyError("Failed to parse answer.");
+      return;
+    }
+
+    rtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface> observer(
+        new rtc::RefCountedObject<
+            CvdOnSetRemoteDescription>([this](webrtc::RTCError error) {
+          if (!error.ok()) {
+            LogAndReplyError(error.message());
+            // The remote description was rejected, this client can't be
+            // trusted anymore.
+            Close();
+            return;
+          }
+          remote_description_added_ = true;
+          AddPendingIceCandidates();
+          peer_connection_->CreateAnswer(
+              // No memory leak here because this is a ref counted objects and
+              // the peer connection immediately wraps it with a scoped_refptr
+              new rtc::RefCountedObject<CvdCreateSessionDescriptionObserver>(
+                  weak_from_this()),
+              webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
+        }));
+    peer_connection_->SetRemoteDescription(std::move(remote_desc), observer);
+    state_ = State::kConnecting;
   } else if (type == "answer") {
     if (state_ != State::kAwaitingAnswer) {
       LogAndReplyError("Received unexpected SDP answer");
@@ -611,6 +688,8 @@
               }
             }));
     peer_connection_->SetRemoteDescription(std::move(remote_desc), observer);
+    remote_description_added_ = true;
+    AddPendingIceCandidates();
     state_ = State::kConnecting;
 
   } else if (type == "ice-candidate") {
@@ -648,12 +727,21 @@
       LogAndReplyError("Failed to parse ICE candidate");
       return;
     }
-    peer_connection_->AddIceCandidate(std::move(candidate),
-                                      [this](webrtc::RTCError error) {
-                                        if (!error.ok()) {
-                                          LogAndReplyError(error.message());
-                                        }
-                                      });
+    if (remote_description_added_) {
+      peer_connection_->AddIceCandidate(std::move(candidate),
+                                        [this](webrtc::RTCError error) {
+                                          if (!error.ok()) {
+                                            LogAndReplyError(error.message());
+                                          }
+                                        });
+    } else {
+      // Store the ice candidate to be added later if it arrives before the
+      // remote description. This could happen if the client uses polling
+      // instead of websockets because the candidates are generated immediately
+      // after the remote (offer) description is set and the events and the ajax
+      // calls are asynchronous.
+      pending_ice_candidates_.push_back(std::move(candidate));
+    }
   } else {
     LogAndReplyError("Unknown client message type: " + type);
     return;
@@ -820,11 +908,22 @@
 }
 void ClientHandler::OnTrack(
     rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {
-  // ignore
+  auto track = transceiver->receiver()->track();
+  if (track && track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) {
+    // It's ok to take the raw pointer here because we make sure to unset it
+    // when the track is removed
+    camera_track_->SetVideoTrack(
+        static_cast<webrtc::VideoTrackInterface *>(track.get()));
+  }
 }
 void ClientHandler::OnRemoveTrack(
     rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) {
-  // ignore
+  auto track = receiver->track();
+  if (track && track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) {
+    // this only unsets if the track matches the one already in store
+    camera_track_->UnsetVideoTrack(
+        reinterpret_cast<webrtc::VideoTrackInterface *>(track.get()));
+  }
 }
 
 }  // namespace webrtc_streaming
diff --git a/host/frontend/webrtc/lib/client_handler.h b/host/frontend/webrtc/lib/client_handler.h
index f7f587b..ea58552 100644
--- a/host/frontend/webrtc/lib/client_handler.h
+++ b/host/frontend/webrtc/lib/client_handler.h
@@ -40,6 +40,9 @@
 class BluetoothChannelHandler;
 class CameraChannelHandler;
 
+class ClientVideoTrackInterface;
+class ClientVideoTrackImpl;
+
 class ClientHandler : public webrtc::PeerConnectionObserver,
                       public std::enable_shared_from_this<ClientHandler> {
  public:
@@ -58,7 +61,7 @@
   bool AddAudio(rtc::scoped_refptr<webrtc::AudioTrackInterface> track,
                   const std::string& label);
 
-  webrtc::VideoTrackInterface* GetCameraStream() const;
+  ClientVideoTrackInterface* GetCameraStream();
 
   void HandleMessage(const Json::Value& client_message);
 
@@ -117,6 +120,7 @@
   void Close();
 
   void LogAndReplyError(const std::string& error_msg) const;
+  void AddPendingIceCandidates();
 
   int client_id_;
   State state_ = State::kNew;
@@ -130,6 +134,18 @@
   std::unique_ptr<ControlChannelHandler> control_handler_;
   std::unique_ptr<BluetoothChannelHandler> bluetooth_handler_;
   std::unique_ptr<CameraChannelHandler> camera_data_handler_;
+  std::unique_ptr<ClientVideoTrackImpl> camera_track_;
+  bool remote_description_added_ = false;
+  std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
+      pending_ice_candidates_;
+};
+
+class ClientVideoTrackInterface {
+ public:
+  virtual ~ClientVideoTrackInterface() = default;
+  virtual void AddOrUpdateSink(
+      rtc::VideoSinkInterface<webrtc::VideoFrame>* sink,
+      const rtc::VideoSinkWants& wants) = 0;
 };
 
 }  // namespace webrtc_streaming
diff --git a/host/frontend/webrtc/lib/server_connection.cpp b/host/frontend/webrtc/lib/server_connection.cpp
new file mode 100644
index 0000000..314b6df
--- /dev/null
+++ b/host/frontend/webrtc/lib/server_connection.cpp
@@ -0,0 +1,650 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+//
+
+#include "host/frontend/webrtc/lib/server_connection.h"
+
+#include <android-base/logging.h>
+#include <libwebsockets.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/files.h"
+
+namespace cuttlefish {
+namespace webrtc_streaming {
+
+// ServerConnection over Unix socket
+class UnixServerConnection : public ServerConnection {
+ public:
+  UnixServerConnection(const std::string& addr,
+                       std::weak_ptr<ServerConnectionObserver> observer);
+  ~UnixServerConnection() override;
+
+  bool Send(const Json::Value& msg) override;
+
+ private:
+  void Connect() override;
+  void StopThread();
+  void ReadLoop();
+
+  const std::string addr_;
+  SharedFD conn_;
+  std::mutex write_mtx_;
+  std::weak_ptr<ServerConnectionObserver> observer_;
+  // The event fd must be declared before the thread to ensure it's initialized
+  // before the thread starts and is safe to be accessed from it.
+  SharedFD thread_notifier_;
+  std::atomic_bool running_ = false;
+  std::thread thread_;
+};
+
+// ServerConnection using websockets
+class WsConnectionContext;
+
+class WsConnection : public std::enable_shared_from_this<WsConnection> {
+ public:
+  struct CreateConnectionSul {
+    lws_sorted_usec_list_t sul = {};
+    std::weak_ptr<WsConnection> weak_this;
+  };
+
+  WsConnection(int port, const std::string& addr, const std::string& path,
+               ServerConfig::Security secure,
+               const std::vector<std::pair<std::string, std::string>>& headers,
+               std::weak_ptr<ServerConnectionObserver> observer,
+               std::shared_ptr<WsConnectionContext> context);
+
+  ~WsConnection();
+
+  void Connect();
+  bool Send(const Json::Value& msg);
+
+  void ConnectInner();
+
+  void OnError(const std::string& error);
+  void OnReceive(const uint8_t* data, size_t len, bool is_binary);
+  void OnOpen();
+  void OnClose();
+  void OnWriteable();
+
+  void AddHttpHeaders(unsigned char** p, unsigned char* end) const;
+
+ private:
+  struct WsBuffer {
+    WsBuffer() = default;
+    WsBuffer(const uint8_t* data, size_t len, bool binary)
+        : buffer_(LWS_PRE + len), is_binary_(binary) {
+      memcpy(&buffer_[LWS_PRE], data, len);
+    }
+
+    uint8_t* data() { return &buffer_[LWS_PRE]; }
+    bool is_binary() const { return is_binary_; }
+    size_t size() const { return buffer_.size() - LWS_PRE; }
+
+   private:
+    std::vector<uint8_t> buffer_;
+    bool is_binary_;
+  };
+  bool Send(const uint8_t* data, size_t len, bool binary = false);
+
+  CreateConnectionSul extended_sul_;
+  struct lws* wsi_;
+  const int port_;
+  const std::string addr_;
+  const std::string path_;
+  const ServerConfig::Security security_;
+  const std::vector<std::pair<std::string, std::string>> headers_;
+
+  std::weak_ptr<ServerConnectionObserver> observer_;
+
+  // each element contains the data to be sent and whether it's binary or not
+  std::deque<WsBuffer> write_queue_;
+  std::mutex write_queue_mutex_;
+  // The connection object should not outlive the context object. This reference
+  // guarantees it.
+  std::shared_ptr<WsConnectionContext> context_;
+};
+
+class WsConnectionContext
+    : public std::enable_shared_from_this<WsConnectionContext> {
+ public:
+  static std::shared_ptr<WsConnectionContext> Create();
+
+  WsConnectionContext(struct lws_context* lws_ctx);
+  ~WsConnectionContext();
+
+  std::unique_ptr<ServerConnection> CreateConnection(
+      int port, const std::string& addr, const std::string& path,
+      ServerConfig::Security secure,
+      std::weak_ptr<ServerConnectionObserver> observer,
+      const std::vector<std::pair<std::string, std::string>>& headers);
+
+  void RememberConnection(void*, std::weak_ptr<WsConnection>);
+  void ForgetConnection(void*);
+  std::shared_ptr<WsConnection> GetConnection(void*);
+
+  struct lws_context* lws_context() {
+    return lws_context_;
+  }
+
+ private:
+  void Start();
+
+  std::map<void*, std::weak_ptr<WsConnection>> weak_by_ptr_;
+  std::mutex map_mutex_;
+  struct lws_context* lws_context_;
+  std::thread message_loop_;
+};
+
+std::unique_ptr<ServerConnection> ServerConnection::Connect(
+    const ServerConfig& conf,
+    std::weak_ptr<ServerConnectionObserver> observer) {
+  std::unique_ptr<ServerConnection> ret;
+  // If the provided address points to an existing UNIX socket in the file
+  // system connect to it, otherwise assume it's a network address and connect
+  // using websockets
+  if (FileIsSocket(conf.addr)) {
+    ret.reset(new UnixServerConnection(conf.addr, observer));
+  } else {
+    // This can be a local variable since the ws connection will keep a
+    // reference to it.
+    auto ws_context = WsConnectionContext::Create();
+    CHECK(ws_context) << "Failed to create websocket context";
+    ret = ws_context->CreateConnection(conf.port, conf.addr, conf.path,
+                                       conf.security, observer,
+                                       conf.http_headers);
+  }
+  ret->Connect();
+  return ret;
+}
+
+void ServerConnection::Reconnect() { Connect(); }
+
+// UnixServerConnection implementation
+
+UnixServerConnection::UnixServerConnection(
+    const std::string& addr, std::weak_ptr<ServerConnectionObserver> observer)
+    : addr_(addr), observer_(observer) {}
+
+UnixServerConnection::~UnixServerConnection() {
+  StopThread();
+}
+
+bool UnixServerConnection::Send(const Json::Value& msg) {
+  Json::StreamWriterBuilder factory;
+  auto str = Json::writeString(factory, msg);
+  std::lock_guard<std::mutex> lock(write_mtx_);
+  auto res =
+      conn_->Send(reinterpret_cast<const uint8_t*>(str.c_str()), str.size(), 0);
+  if (res < 0) {
+    LOG(ERROR) << "Failed to send data to signaling server: "
+               << conn_->StrError();
+    // Don't call OnError() here, the receiving thread probably did it already
+    // or is about to do it.
+  }
+  // A SOCK_SEQPACKET unix socket will send the entire message or fail, but it
+  // won't send a partial message.
+  return res == str.size();
+}
+
+void UnixServerConnection::Connect() {
+  // The thread could be running if this is a Reconnect
+  StopThread();
+
+  conn_ = SharedFD::SocketLocalClient(addr_, false, SOCK_SEQPACKET);
+  if (!conn_->IsOpen()) {
+    LOG(ERROR) << "Failed to connect to unix socket: " << conn_->StrError();
+    if (auto o = observer_.lock(); o) {
+      o->OnError("Failed to connect to unix socket");
+    }
+    return;
+  }
+  thread_notifier_ = SharedFD::Event();
+  if (!thread_notifier_->IsOpen()) {
+    LOG(ERROR) << "Failed to create eventfd for background thread: "
+               << thread_notifier_->StrError();
+    if (auto o = observer_.lock(); o) {
+      o->OnError("Failed to create eventfd for background thread");
+    }
+    return;
+  }
+  if (auto o = observer_.lock(); o) {
+    o->OnOpen();
+  }
+  // Start the thread
+  running_ = true;
+  thread_ = std::thread([this](){ReadLoop();});
+}
+
+void UnixServerConnection::StopThread() {
+  running_ = false;
+  if (!thread_notifier_->IsOpen()) {
+    // The thread won't be running if this isn't open
+    return;
+  }
+  if (thread_notifier_->EventfdWrite(1) < 0) {
+    LOG(ERROR) << "Failed to notify background thread, this thread may block";
+  }
+  if (thread_.joinable()) {
+    thread_.join();
+  }
+}
+
+void UnixServerConnection::ReadLoop() {
+  if (!thread_notifier_->IsOpen()) {
+    LOG(ERROR) << "The UnixServerConnection's background thread is unable to "
+                  "receive notifications so it can't run";
+    return;
+  }
+  std::vector<uint8_t> buffer(4096, 0);
+  while (running_) {
+    SharedFDSet rset;
+    rset.Set(thread_notifier_);
+    rset.Set(conn_);
+    auto res = Select(&rset, nullptr, nullptr, nullptr);
+    if (res < 0) {
+      LOG(ERROR) << "Failed to select from background thread";
+      break;
+    }
+    if (rset.IsSet(thread_notifier_)) {
+      eventfd_t val;
+      auto res = thread_notifier_->EventfdRead(&val);
+      if (res < 0) {
+        LOG(ERROR) << "Error reading from event fd: "
+                   << thread_notifier_->StrError();
+        break;
+      }
+    }
+    if (rset.IsSet(conn_)) {
+      auto size = conn_->Recv(buffer.data(), 0, MSG_TRUNC | MSG_PEEK);
+      if (size > buffer.size()) {
+        // Enlarge enough to accommodate size bytes and be a multiple of 4096
+        auto new_size = (size + 4095) & ~4095;
+        buffer.resize(new_size);
+      }
+      auto res = conn_->Recv(buffer.data(), buffer.size(), MSG_TRUNC);
+      if (res < 0) {
+        LOG(ERROR) << "Failed to read from server: " << conn_->StrError();
+        if (auto observer = observer_.lock(); observer) {
+          observer->OnError(conn_->StrError());
+        }
+        return;
+      }
+      if (res == 0) {
+        auto observer = observer_.lock();
+        if (observer) {
+          observer->OnClose();
+        }
+        break;
+      }
+      auto observer = observer_.lock();
+      if (observer) {
+        observer->OnReceive(buffer.data(), res, false);
+      }
+    }
+  }
+}
+
+// WsConnection implementation
+
+int LwsCallback(struct lws* wsi, enum lws_callback_reasons reason, void* user,
+                void* in, size_t len);
+void CreateConnectionCallback(lws_sorted_usec_list_t* sul);
+
+namespace {
+
+constexpr char kProtocolName[] = "cf-webrtc-device";
+constexpr int kBufferSize = 65536;
+
+const uint32_t backoff_ms[] = {1000, 2000, 3000, 4000, 5000};
+
+const lws_retry_bo_t kRetry = {
+    .retry_ms_table = backoff_ms,
+    .retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms),
+    .conceal_count = LWS_ARRAY_SIZE(backoff_ms),
+
+    .secs_since_valid_ping = 3,    /* force PINGs after secs idle */
+    .secs_since_valid_hangup = 10, /* hangup after secs idle */
+
+    .jitter_percent = 20,
+};
+
+const struct lws_protocols kProtocols[2] = {
+    {kProtocolName, LwsCallback, 0, kBufferSize, 0, NULL, 0},
+    {NULL, NULL, 0, 0, 0, NULL, 0}};
+
+}  // namespace
+
+std::shared_ptr<WsConnectionContext> WsConnectionContext::Create() {
+  struct lws_context_creation_info context_info = {};
+  context_info.port = CONTEXT_PORT_NO_LISTEN;
+  context_info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+  context_info.protocols = kProtocols;
+  struct lws_context* lws_ctx = lws_create_context(&context_info);
+  if (!lws_ctx) {
+    return nullptr;
+  }
+  return std::shared_ptr<WsConnectionContext>(new WsConnectionContext(lws_ctx));
+}
+
+WsConnectionContext::WsConnectionContext(struct lws_context* lws_ctx)
+    : lws_context_(lws_ctx) {
+  Start();
+}
+
+WsConnectionContext::~WsConnectionContext() {
+  lws_context_destroy(lws_context_);
+  if (message_loop_.joinable()) {
+    message_loop_.join();
+  }
+}
+
+void WsConnectionContext::Start() {
+  message_loop_ = std::thread([this]() {
+    for (;;) {
+      if (lws_service(lws_context_, 0) < 0) {
+        break;
+      }
+    }
+  });
+}
+
+// This wrapper is needed because the ServerConnection objects are meant to be
+// referenced by std::unique_ptr but WsConnection needs to be referenced by
+// std::shared_ptr because it's also (weakly) referenced by the websocket
+// thread.
+class WsConnectionWrapper : public ServerConnection {
+ public:
+  WsConnectionWrapper(std::shared_ptr<WsConnection> conn) : conn_(conn) {}
+
+  bool Send(const Json::Value& msg) override { return conn_->Send(msg); }
+
+ private:
+  void Connect() override { return conn_->Connect(); }
+  std::shared_ptr<WsConnection> conn_;
+};
+
+std::unique_ptr<ServerConnection> WsConnectionContext::CreateConnection(
+    int port, const std::string& addr, const std::string& path,
+    ServerConfig::Security security,
+    std::weak_ptr<ServerConnectionObserver> observer,
+    const std::vector<std::pair<std::string, std::string>>& headers) {
+  return std::unique_ptr<ServerConnection>(
+      new WsConnectionWrapper(std::make_shared<WsConnection>(
+          port, addr, path, security, headers, observer, shared_from_this())));
+}
+
+std::shared_ptr<WsConnection> WsConnectionContext::GetConnection(void* raw) {
+  std::shared_ptr<WsConnection> connection;
+  {
+    std::lock_guard<std::mutex> lock(map_mutex_);
+    if (weak_by_ptr_.count(raw) == 0) {
+      return nullptr;
+    }
+    connection = weak_by_ptr_[raw].lock();
+    if (!connection) {
+      weak_by_ptr_.erase(raw);
+    }
+  }
+  return connection;
+}
+
+void WsConnectionContext::RememberConnection(void* raw,
+                                             std::weak_ptr<WsConnection> conn) {
+  std::lock_guard<std::mutex> lock(map_mutex_);
+  weak_by_ptr_.emplace(
+      std::pair<void*, std::weak_ptr<WsConnection>>(raw, conn));
+}
+
+void WsConnectionContext::ForgetConnection(void* raw) {
+  std::lock_guard<std::mutex> lock(map_mutex_);
+  weak_by_ptr_.erase(raw);
+}
+
+WsConnection::WsConnection(
+    int port, const std::string& addr, const std::string& path,
+    ServerConfig::Security security,
+    const std::vector<std::pair<std::string, std::string>>& headers,
+    std::weak_ptr<ServerConnectionObserver> observer,
+    std::shared_ptr<WsConnectionContext> context)
+    : port_(port),
+      addr_(addr),
+      path_(path),
+      security_(security),
+      headers_(headers),
+      observer_(observer),
+      context_(context) {}
+
+WsConnection::~WsConnection() {
+  context_->ForgetConnection(this);
+  // This will cause the callback to be called which will drop the connection
+  // after seeing the context doesn't remember this object
+  lws_callback_on_writable(wsi_);
+}
+
+void WsConnection::Connect() {
+  memset(&extended_sul_.sul, 0, sizeof(extended_sul_.sul));
+  extended_sul_.weak_this = weak_from_this();
+  lws_sul_schedule(context_->lws_context(), 0, &extended_sul_.sul,
+                   CreateConnectionCallback, 1);
+}
+
+void WsConnection::AddHttpHeaders(unsigned char** p, unsigned char* end) const {
+  for (const auto& header_entry : headers_) {
+    const auto& name = header_entry.first;
+    const auto& value = header_entry.second;
+    auto res = lws_add_http_header_by_name(
+        wsi_, reinterpret_cast<const unsigned char*>(name.c_str()),
+        reinterpret_cast<const unsigned char*>(value.c_str()), value.size(), p,
+        end);
+    if (res != 0) {
+      LOG(ERROR) << "Unable to add header: " << name;
+    }
+  }
+  if (!headers_.empty()) {
+    // Let LWS know we added some headers.
+    lws_client_http_body_pending(wsi_, 1);
+  }
+}
+
+void WsConnection::OnError(const std::string& error) {
+  auto observer = observer_.lock();
+  if (observer) {
+    observer->OnError(error);
+  }
+}
+void WsConnection::OnReceive(const uint8_t* data, size_t len, bool is_binary) {
+  auto observer = observer_.lock();
+  if (observer) {
+    observer->OnReceive(data, len, is_binary);
+  }
+}
+void WsConnection::OnOpen() {
+  auto observer = observer_.lock();
+  if (observer) {
+    observer->OnOpen();
+  }
+}
+void WsConnection::OnClose() {
+  auto observer = observer_.lock();
+  if (observer) {
+    observer->OnClose();
+  }
+}
+
+void WsConnection::OnWriteable() {
+  WsBuffer buffer;
+  {
+    std::lock_guard<std::mutex> lock(write_queue_mutex_);
+    if (write_queue_.size() == 0) {
+      return;
+    }
+    buffer = std::move(write_queue_.front());
+    write_queue_.pop_front();
+  }
+  auto flags = lws_write_ws_flags(
+      buffer.is_binary() ? LWS_WRITE_BINARY : LWS_WRITE_TEXT, true, true);
+  auto res = lws_write(wsi_, buffer.data(), buffer.size(),
+                       (enum lws_write_protocol)flags);
+  if (res != buffer.size()) {
+    LOG(WARNING) << "Unable to send the entire message!";
+  }
+}
+
+bool WsConnection::Send(const Json::Value& msg) {
+  Json::StreamWriterBuilder factory;
+  auto str = Json::writeString(factory, msg);
+  return Send(reinterpret_cast<const uint8_t*>(str.c_str()), str.size());
+}
+
+bool WsConnection::Send(const uint8_t* data, size_t len, bool binary) {
+  if (!wsi_) {
+    LOG(WARNING) << "Send called on an uninitialized connection!!";
+    return false;
+  }
+  WsBuffer buffer(data, len, binary);
+  {
+    std::lock_guard<std::mutex> lock(write_queue_mutex_);
+    write_queue_.emplace_back(std::move(buffer));
+  }
+
+  lws_callback_on_writable(wsi_);
+  return true;
+}
+
+int LwsCallback(struct lws* wsi, enum lws_callback_reasons reason, void* user,
+                void* in, size_t len) {
+  constexpr int DROP = -1;
+  constexpr int OK = 0;
+
+  // For some values of `reason`, `user` doesn't point to the value provided
+  // when the connection was created. This function object should be used with
+  // care.
+  auto with_connection =
+      [wsi, user](std::function<void(std::shared_ptr<WsConnection>)> cb) {
+        auto context = reinterpret_cast<WsConnectionContext*>(user);
+        auto connection = context->GetConnection(wsi);
+        if (!connection) {
+          return DROP;
+        }
+        cb(connection);
+        return OK;
+      };
+
+  switch (reason) {
+    case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+      return with_connection([in](std::shared_ptr<WsConnection> connection) {
+        connection->OnError(in ? (char*)in : "(null)");
+      });
+
+    case LWS_CALLBACK_CLIENT_RECEIVE:
+      return with_connection(
+          [in, len, wsi](std::shared_ptr<WsConnection> connection) {
+            connection->OnReceive((const uint8_t*)in, len,
+                                  lws_frame_is_binary(wsi));
+          });
+
+    case LWS_CALLBACK_CLIENT_ESTABLISHED:
+      return with_connection([](std::shared_ptr<WsConnection> connection) {
+        connection->OnOpen();
+      });
+
+    case LWS_CALLBACK_CLIENT_CLOSED:
+      return with_connection([](std::shared_ptr<WsConnection> connection) {
+        connection->OnClose();
+      });
+
+    case LWS_CALLBACK_CLIENT_WRITEABLE:
+      return with_connection([](std::shared_ptr<WsConnection> connection) {
+        connection->OnWriteable();
+      });
+
+    case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
+      return with_connection(
+          [in, len](std::shared_ptr<WsConnection> connection) {
+            auto p = reinterpret_cast<unsigned char**>(in);
+            auto end = (*p) + len;
+            connection->AddHttpHeaders(p, end);
+          });
+
+    case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
+      // This callback is only called when we add additional HTTP headers, let
+      // LWS know we're done modifying the HTTP request.
+      lws_client_http_body_pending(wsi, 0);
+      return 0;
+
+    default:
+      LOG(VERBOSE) << "Unhandled value: " << reason;
+      return lws_callback_http_dummy(wsi, reason, user, in, len);
+  }
+}
+
+void CreateConnectionCallback(lws_sorted_usec_list_t* sul) {
+  std::shared_ptr<WsConnection> connection =
+      reinterpret_cast<WsConnection::CreateConnectionSul*>(sul)
+          ->weak_this.lock();
+  if (!connection) {
+    LOG(WARNING) << "The object was already destroyed by the time of the first "
+                 << "connection attempt. That's unusual.";
+    return;
+  }
+  connection->ConnectInner();
+}
+
+void WsConnection::ConnectInner() {
+  struct lws_client_connect_info connect_info;
+
+  memset(&connect_info, 0, sizeof(connect_info));
+
+  connect_info.context = context_->lws_context();
+  connect_info.port = port_;
+  connect_info.address = addr_.c_str();
+  connect_info.path = path_.c_str();
+  connect_info.host = connect_info.address;
+  connect_info.origin = connect_info.address;
+  switch (security_) {
+    case ServerConfig::Security::kAllowSelfSigned:
+      connect_info.ssl_connection = LCCSCF_ALLOW_SELFSIGNED |
+                                    LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK |
+                                    LCCSCF_USE_SSL;
+      break;
+    case ServerConfig::Security::kStrict:
+      connect_info.ssl_connection = LCCSCF_USE_SSL;
+      break;
+    case ServerConfig::Security::kInsecure:
+      connect_info.ssl_connection = 0;
+      break;
+  }
+  connect_info.protocol = "webrtc-operator";
+  connect_info.local_protocol_name = kProtocolName;
+  connect_info.pwsi = &wsi_;
+  connect_info.retry_and_idle_policy = &kRetry;
+  // There is no guarantee the connection object still exists when the callback
+  // is called. Put the context instead as the user data which is guaranteed to
+  // still exist and holds a weak ptr to the connection.
+  connect_info.userdata = context_.get();
+
+  if (lws_client_connect_via_info(&connect_info)) {
+    // wsi_ is not initialized until after the call to
+    // lws_client_connect_via_info(). Luckily, this is guaranteed to run before
+    // the protocol callback is called because it runs in the same loop.
+    context_->RememberConnection(wsi_, weak_from_this());
+  } else {
+    LOG(ERROR) << "Connection failed!";
+  }
+}
+
+}  // namespace webrtc_streaming
+}  // namespace cuttlefish
diff --git a/host/frontend/webrtc/lib/server_connection.h b/host/frontend/webrtc/lib/server_connection.h
new file mode 100644
index 0000000..ab7111f
--- /dev/null
+++ b/host/frontend/webrtc/lib/server_connection.h
@@ -0,0 +1,93 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+//
+
+#pragma once
+
+#include <string.h>
+
+#include <deque>
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <json/json.h>
+#include <libwebsockets.h>
+
+namespace cuttlefish {
+namespace webrtc_streaming {
+
+struct ServerConfig {
+  enum class Security {
+    kInsecure,
+    kAllowSelfSigned,
+    kStrict,
+  };
+
+  // The ip address or domain name of the operator server.
+  std::string addr;
+  int port;
+  // The path component of the operator server's register url.
+  std::string path;
+  // The security level to use when connecting to the operator server.
+  Security security;
+  // A list of key value pairs to include as HTTP handshake headers when
+  // connecting to the operator.
+  std::vector<std::pair<std::string, std::string>> http_headers;
+};
+
+class ServerConnectionObserver {
+ public:
+  virtual ~ServerConnectionObserver() = default;
+  // Called when the connection to the server has been established. This is the
+  // cue to start using Send().
+  virtual void OnOpen() = 0;
+  virtual void OnClose() = 0;
+  // Called when the connection to the server has failed with an unrecoverable
+  // error.
+  virtual void OnError(const std::string& error) = 0;
+  virtual void OnReceive(const uint8_t* msg, size_t length, bool is_binary) = 0;
+};
+
+// Represents a connection to the signaling server. When a connection is created
+// it connects with the server automatically but sends no info.
+// Only Send() can be called from multiple threads simultaneously. Reconnect(),
+// Send() and the destructor will run into race conditions if called
+// concurrently.
+class ServerConnection {
+ public:
+  static std::unique_ptr<ServerConnection> Connect(
+      const ServerConfig& conf,
+      std::weak_ptr<ServerConnectionObserver> observer);
+
+  // Destroying the connection will disconnect from the signaling server and
+  // release any open fds.
+  virtual ~ServerConnection() = default;
+
+  // Sends data to the server encoded as JSON.
+  virtual bool Send(const Json::Value&) = 0;
+
+  // makes re-connect request
+  virtual void Reconnect();
+
+ private:
+  virtual void Connect() = 0;
+};
+
+}  // namespace webrtc_streaming
+}  // namespace cuttlefish
diff --git a/host/frontend/webrtc/lib/streamer.cpp b/host/frontend/webrtc/lib/streamer.cpp
index 71bbab5..088b25b 100644
--- a/host/frontend/webrtc/lib/streamer.cpp
+++ b/host/frontend/webrtc/lib/streamer.cpp
@@ -62,12 +62,10 @@
 constexpr auto kControlPanelButtonHingeAngleValue = "hinge_angle_value";
 constexpr auto kCustomControlPanelButtonsField = "custom_control_panel_buttons";
 
-void SendJson(WsConnection* ws_conn, const Json::Value& data) {
-  Json::StreamWriterBuilder factory;
-  auto data_str = Json::writeString(factory, data);
-  ws_conn->Send(reinterpret_cast<const uint8_t*>(data_str.c_str()),
-                data_str.size());
-}
+constexpr int kRegistrationRetries = 3;
+constexpr int kRetryFirstIntervalMs = 1000;
+constexpr int kReconnectRetries = 100;
+constexpr int kReconnectIntervalMs = 1000;
 
 bool ParseMessage(const uint8_t* data, size_t length, Json::Value* msg_out) {
   auto str = reinterpret_cast<const char*>(data);
@@ -136,10 +134,14 @@
 
 }  // namespace
 
-class Streamer::Impl : public WsConnectionObserver {
+
+class Streamer::Impl : public ServerConnectionObserver,
+                       public std::enable_shared_from_this<ServerConnectionObserver> {
  public:
   std::shared_ptr<ClientHandler> CreateClientHandler(int client_id);
 
+  void Register(std::weak_ptr<OperatorObserver> observer);
+
   void SendMessageToClient(int client_id, const Json::Value& msg);
   void DestroyClientHandler(int client_id);
   void SetupCameraForClient(int client_id);
@@ -157,7 +159,7 @@
   // no need for extra synchronization mechanisms (mutex)
   StreamerConfig config_;
   OperatorServerConfig operator_config_;
-  std::shared_ptr<WsConnection> server_connection_;
+  std::unique_ptr<ServerConnection> server_connection_;
   std::shared_ptr<ConnectionObserverFactory> connection_observer_factory_;
   rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
       peer_connection_factory_;
@@ -173,6 +175,8 @@
   std::vector<ControlPanelButtonDescriptor> custom_control_panel_buttons_;
   std::shared_ptr<AudioDeviceModuleWrapper> audio_device_module_;
   std::unique_ptr<CameraStreamer> camera_streamer_;
+  int registration_retries_left_ = kRegistrationRetries;
+  int retry_interval_ms_ = kRetryFirstIntervalMs;
 };
 
 Streamer::Streamer(std::unique_ptr<Streamer::Impl> impl)
@@ -307,22 +311,7 @@
   // No need to block the calling thread on this, the observer will be notified
   // when the connection is established.
   impl_->signal_thread_->PostTask(RTC_FROM_HERE, [this, observer]() {
-    impl_->operator_observer_ = observer;
-    // This can be a local variable since the connection object will keep a
-    // reference to it.
-    auto ws_context = WsConnectionContext::Create();
-    CHECK(ws_context) << "Failed to create websocket context";
-    impl_->server_connection_ = ws_context->CreateConnection(
-        impl_->config_.operator_server.port,
-        impl_->config_.operator_server.addr,
-        impl_->config_.operator_server.path,
-        impl_->config_.operator_server.security, impl_,
-        impl_->config_.operator_server.http_headers);
-
-    CHECK(impl_->server_connection_)
-        << "Unable to create websocket connection object";
-
-    impl_->server_connection_->Connect();
+    impl_->Register(observer);
   });
 }
 
@@ -345,6 +334,21 @@
   }
 }
 
+void Streamer::Impl::Register(std::weak_ptr<OperatorObserver> observer) {
+  operator_observer_ = observer;
+  // When the connection is established the OnOpen function will be called where
+  // the registration will take place
+  if (!server_connection_) {
+    server_connection_ =
+        ServerConnection::Connect(config_.operator_server, weak_from_this());
+  } else {
+    // in case connection attempt is retried, just call Reconnect().
+    // Recreating server_connection_ object will destroy existing WSConnection
+    // object and task re-scheduling will fail
+    server_connection_->Reconnect();
+  }
+}
+
 void Streamer::Impl::OnOpen() {
   // Called from the websocket thread.
   // Connected to operator.
@@ -354,6 +358,9 @@
         cuttlefish::webrtc_signaling::kRegisterType;
     register_obj[cuttlefish::webrtc_signaling::kDeviceIdField] =
         config_.device_id;
+    CHECK(config_.client_files_port >= 0) << "Invalide device port provided";
+    register_obj[cuttlefish::webrtc_signaling::kDevicePortField] =
+        config_.client_files_port;
 
     Json::Value device_info;
     Json::Value displays(Json::ValueType::arrayValue);
@@ -409,7 +416,7 @@
     }
     device_info[kCustomControlPanelButtonsField] = custom_control_panel_buttons;
     register_obj[cuttlefish::webrtc_signaling::kDeviceInfoField] = device_info;
-    SendJson(server_connection_.get(), register_obj);
+    server_connection_->Send(register_obj);
     // Do this last as OnRegistered() is user code and may take some time to
     // complete (although it shouldn't...)
     auto observer = operator_observer_.lock();
@@ -423,24 +430,45 @@
   // Called from websocket thread
   // The operator shouldn't close the connection with the client, it's up to the
   // device to decide when to disconnect.
-  LOG(WARNING) << "Websocket closed unexpectedly";
+  LOG(WARNING) << "Connection with server closed unexpectedly";
   signal_thread_->PostTask(RTC_FROM_HERE, [this]() {
     auto observer = operator_observer_.lock();
     if (observer) {
       observer->OnClose();
     }
   });
+  LOG(INFO) << "Trying to re-connect to operator..";
+  registration_retries_left_ = kReconnectRetries;
+  retry_interval_ms_ = kReconnectIntervalMs;
+  signal_thread_->PostDelayedTask(
+      RTC_FROM_HERE, [this]() { Register(operator_observer_); },
+      retry_interval_ms_);
 }
 
 void Streamer::Impl::OnError(const std::string& error) {
   // Called from websocket thread.
-  LOG(ERROR) << "Error on connection with the operator: " << error;
-  signal_thread_->PostTask(RTC_FROM_HERE, [this]() {
-    auto observer = operator_observer_.lock();
-    if (observer) {
-      observer->OnError();
-    }
-  });
+  if (registration_retries_left_) {
+    LOG(WARNING) << "Connection to operator failed (" << error << "), "
+                 << registration_retries_left_ << " retries left"
+                 << " (will retry in " << retry_interval_ms_ / 1000 << "s)";
+    --registration_retries_left_;
+    signal_thread_->PostDelayedTask(
+        RTC_FROM_HERE,
+        [this]() {
+          // Need to reconnect and register again with operator
+          Register(operator_observer_);
+        },
+        retry_interval_ms_);
+    retry_interval_ms_ *= 2;
+  } else {
+    LOG(ERROR) << "Error on connection with the operator: " << error;
+    signal_thread_->PostTask(RTC_FROM_HERE, [this]() {
+      auto observer = operator_observer_.lock();
+      if (observer) {
+        observer->OnError();
+      }
+    });
+  }
 }
 
 void Streamer::Impl::HandleConfigMessage(const Json::Value& server_message) {
@@ -635,8 +663,8 @@
       cuttlefish::webrtc_signaling::kForwardType;
   wrapper[cuttlefish::webrtc_signaling::kClientIdField] = client_id;
   // This is safe to call from the webrtc threads because
-  // WsConnection is thread safe
-  SendJson(server_connection_.get(), wrapper);
+  // ServerConnection(s) are thread safe
+  server_connection_->Send(wrapper);
 }
 
 void Streamer::Impl::DestroyClientHandler(int client_id) {
diff --git a/host/frontend/webrtc/lib/streamer.h b/host/frontend/webrtc/lib/streamer.h
index 50a7e12..bc2297e 100644
--- a/host/frontend/webrtc/lib/streamer.h
+++ b/host/frontend/webrtc/lib/streamer.h
@@ -32,7 +32,7 @@
 #include "host/frontend/webrtc/lib/connection_observer.h"
 #include "host/frontend/webrtc/lib/local_recorder.h"
 #include "host/frontend/webrtc/lib/video_sink.h"
-#include "host/frontend/webrtc/lib/ws_connection.h"
+#include "host/frontend/webrtc/lib/server_connection.h"
 
 namespace cuttlefish {
 namespace webrtc_streaming {
@@ -42,18 +42,9 @@
 struct StreamerConfig {
   // The id with which to register with the operator server.
   std::string device_id;
-  struct {
-    // The ip address or domain name of the operator server.
-    std::string addr;
-    int port;
-    // The path component of the operator server's register url.
-    std::string path;
-    // The security level to use when connecting to the operator server.
-    WsConnection::Security security;
-    // A list of key value pairs to include as HTTP handshake headers when
-    // connecting to the operator.
-    std::vector<std::pair<std::string, std::string>> http_headers;
-  } operator_server;
+  // The port on which the client files are being served
+  int client_files_port;
+  ServerConfig operator_server;
   // The port ranges webrtc is allowed to use.
   // [0,0] means all ports
   std::pair<uint16_t, uint16_t> udp_port_range = {15550, 15558};
diff --git a/host/frontend/webrtc/lib/utils.cpp b/host/frontend/webrtc/lib/utils.cpp
index 78460c3..e84b897 100644
--- a/host/frontend/webrtc/lib/utils.cpp
+++ b/host/frontend/webrtc/lib/utils.cpp
@@ -28,6 +28,9 @@
 std::string ValidateField(const Json::Value &obj, const std::string &type,
                           const std::string &field_name,
                           const Json::ValueType &field_type, bool required) {
+  if (!obj.isObject()) {
+    return "Expected object with name-value pairs";
+  }
   if (!obj.isMember(field_name) && !required) {
     return "";
   }
diff --git a/host/frontend/webrtc/lib/ws_connection.cpp b/host/frontend/webrtc/lib/ws_connection.cpp
deleted file mode 100644
index 5303c21..0000000
--- a/host/frontend/webrtc/lib/ws_connection.cpp
+++ /dev/null
@@ -1,446 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-//
-
-#include "host/frontend/webrtc/lib/ws_connection.h"
-
-#include <android-base/logging.h>
-#include <libwebsockets.h>
-
-class WsConnectionContextImpl;
-
-class WsConnectionImpl : public WsConnection,
-                         public std::enable_shared_from_this<WsConnectionImpl> {
- public:
-  struct CreateConnectionSul {
-    lws_sorted_usec_list_t sul = {};
-    std::weak_ptr<WsConnectionImpl> weak_this;
-  };
-
-  WsConnectionImpl(
-      int port, const std::string& addr, const std::string& path,
-      Security secure,
-      const std::vector<std::pair<std::string, std::string>>& headers,
-      std::weak_ptr<WsConnectionObserver> observer,
-      std::shared_ptr<WsConnectionContextImpl> context);
-
-  ~WsConnectionImpl() override;
-
-  void Connect() override;
-  void ConnectInner();
-
-  bool Send(const uint8_t* data, size_t len, bool binary = false) override;
-
-  void OnError(const std::string& error);
-  void OnReceive(const uint8_t* data, size_t len, bool is_binary);
-  void OnOpen();
-  void OnClose();
-  void OnWriteable();
-
-  void AddHttpHeaders(unsigned char** p, unsigned char* end) const;
-
- private:
-  struct WsBuffer {
-    WsBuffer() = default;
-    WsBuffer(const uint8_t* data, size_t len, bool binary)
-        : buffer_(LWS_PRE + len), is_binary_(binary) {
-      memcpy(&buffer_[LWS_PRE], data, len);
-    }
-
-    uint8_t* data() { return &buffer_[LWS_PRE]; }
-    bool is_binary() const { return is_binary_; }
-    size_t size() const { return buffer_.size() - LWS_PRE; }
-
-   private:
-    std::vector<uint8_t> buffer_;
-    bool is_binary_;
-  };
-
-  CreateConnectionSul extended_sul_;
-  struct lws* wsi_;
-  const int port_;
-  const std::string addr_;
-  const std::string path_;
-  const Security security_;
-  const std::vector<std::pair<std::string, std::string>> headers_;
-
-  std::weak_ptr<WsConnectionObserver> observer_;
-
-  // each element contains the data to be sent and whether it's binary or not
-  std::deque<WsBuffer> write_queue_;
-  std::mutex write_queue_mutex_;
-  // The connection object should not outlive the context object. This reference
-  // guarantees it.
-  std::shared_ptr<WsConnectionContextImpl> context_;
-};
-
-class WsConnectionContextImpl
-    : public WsConnectionContext,
-      public std::enable_shared_from_this<WsConnectionContextImpl> {
- public:
-  WsConnectionContextImpl(struct lws_context* lws_ctx);
-  ~WsConnectionContextImpl() override;
-
-  std::shared_ptr<WsConnection> CreateConnection(
-      int port, const std::string& addr, const std::string& path,
-      WsConnection::Security secure,
-      std::weak_ptr<WsConnectionObserver> observer,
-      const std::vector<std::pair<std::string, std::string>>& headers) override;
-
-  void RememberConnection(void*, std::weak_ptr<WsConnectionImpl>);
-  void ForgetConnection(void*);
-  std::shared_ptr<WsConnectionImpl> GetConnection(void*);
-
-  struct lws_context* lws_context() {
-    return lws_context_;
-  }
-
- private:
-  void Start();
-
-  std::map<void*, std::weak_ptr<WsConnectionImpl>> weak_by_ptr_;
-  std::mutex map_mutex_;
-  struct lws_context* lws_context_;
-  std::thread message_loop_;
-};
-
-int LwsCallback(struct lws* wsi, enum lws_callback_reasons reason, void* user,
-                void* in, size_t len);
-void CreateConnectionCallback(lws_sorted_usec_list_t* sul);
-
-namespace {
-
-constexpr char kProtocolName[] = "cf-webrtc-device";
-constexpr int kBufferSize = 65536;
-
-const uint32_t backoff_ms[] = {1000, 2000, 3000, 4000, 5000};
-
-const lws_retry_bo_t kRetry = {
-    .retry_ms_table = backoff_ms,
-    .retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms),
-    .conceal_count = LWS_ARRAY_SIZE(backoff_ms),
-
-    .secs_since_valid_ping = 3,    /* force PINGs after secs idle */
-    .secs_since_valid_hangup = 10, /* hangup after secs idle */
-
-    .jitter_percent = 20,
-};
-
-const struct lws_protocols kProtocols[2] = {
-    {kProtocolName, LwsCallback, 0, kBufferSize, 0, NULL, 0},
-    {NULL, NULL, 0, 0, 0, NULL, 0}};
-
-}  // namespace
-
-std::shared_ptr<WsConnectionContext> WsConnectionContext::Create() {
-  struct lws_context_creation_info context_info = {};
-  context_info.port = CONTEXT_PORT_NO_LISTEN;
-  context_info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-  context_info.protocols = kProtocols;
-  struct lws_context* lws_ctx = lws_create_context(&context_info);
-  if (!lws_ctx) {
-    return nullptr;
-  }
-  return std::shared_ptr<WsConnectionContext>(
-      new WsConnectionContextImpl(lws_ctx));
-}
-
-WsConnectionContextImpl::WsConnectionContextImpl(struct lws_context* lws_ctx)
-    : lws_context_(lws_ctx) {
-  Start();
-}
-
-WsConnectionContextImpl::~WsConnectionContextImpl() {
-  lws_context_destroy(lws_context_);
-  if (message_loop_.joinable()) {
-    message_loop_.join();
-  }
-}
-
-void WsConnectionContextImpl::Start() {
-  message_loop_ = std::thread([this]() {
-    for (;;) {
-      if (lws_service(lws_context_, 0) < 0) {
-        break;
-      }
-    }
-  });
-}
-
-std::shared_ptr<WsConnection> WsConnectionContextImpl::CreateConnection(
-    int port, const std::string& addr, const std::string& path,
-    WsConnection::Security security,
-    std::weak_ptr<WsConnectionObserver> observer,
-    const std::vector<std::pair<std::string, std::string>>& headers) {
-  return std::shared_ptr<WsConnection>(new WsConnectionImpl(
-      port, addr, path, security, headers, observer, shared_from_this()));
-}
-
-std::shared_ptr<WsConnectionImpl> WsConnectionContextImpl::GetConnection(
-    void* raw) {
-  std::shared_ptr<WsConnectionImpl> connection;
-  {
-    std::lock_guard<std::mutex> lock(map_mutex_);
-    if (weak_by_ptr_.count(raw) == 0) {
-      return nullptr;
-    }
-    connection = weak_by_ptr_[raw].lock();
-    if (!connection) {
-      weak_by_ptr_.erase(raw);
-    }
-  }
-  return connection;
-}
-
-void WsConnectionContextImpl::RememberConnection(
-    void* raw, std::weak_ptr<WsConnectionImpl> conn) {
-  std::lock_guard<std::mutex> lock(map_mutex_);
-  weak_by_ptr_.emplace(
-      std::pair<void*, std::weak_ptr<WsConnectionImpl>>(raw, conn));
-}
-
-void WsConnectionContextImpl::ForgetConnection(void* raw) {
-  std::lock_guard<std::mutex> lock(map_mutex_);
-  weak_by_ptr_.erase(raw);
-}
-
-WsConnectionImpl::WsConnectionImpl(
-    int port, const std::string& addr, const std::string& path,
-    Security security,
-    const std::vector<std::pair<std::string, std::string>>& headers,
-    std::weak_ptr<WsConnectionObserver> observer,
-    std::shared_ptr<WsConnectionContextImpl> context)
-    : port_(port),
-      addr_(addr),
-      path_(path),
-      security_(security),
-      headers_(headers),
-      observer_(observer),
-      context_(context) {}
-
-WsConnectionImpl::~WsConnectionImpl() {
-  context_->ForgetConnection(this);
-  // This will cause the callback to be called which will drop the connection
-  // after seeing the context doesn't remember this object
-  lws_callback_on_writable(wsi_);
-}
-
-void WsConnectionImpl::Connect() {
-  memset(&extended_sul_.sul, 0, sizeof(extended_sul_.sul));
-  extended_sul_.weak_this = weak_from_this();
-  lws_sul_schedule(context_->lws_context(), 0, &extended_sul_.sul,
-                   CreateConnectionCallback, 1);
-}
-
-void WsConnectionImpl::AddHttpHeaders(unsigned char** p,
-                                      unsigned char* end) const {
-  for (const auto& header_entry: headers_) {
-    const auto& name = header_entry.first;
-    const auto& value = header_entry.second;
-    auto res = lws_add_http_header_by_name(
-        wsi_, reinterpret_cast<const unsigned char*>(name.c_str()),
-        reinterpret_cast<const unsigned char*>(value.c_str()), value.size(), p,
-        end);
-    if (res != 0) {
-      LOG(ERROR) << "Unable to add header: " << name;
-    }
-  }
-  if (!headers_.empty()) {
-    // Let LWS know we added some headers.
-    lws_client_http_body_pending(wsi_, 1);
-  }
-}
-
-void WsConnectionImpl::OnError(const std::string& error) {
-  auto observer = observer_.lock();
-  if (observer) {
-    observer->OnError(error);
-  }
-}
-void WsConnectionImpl::OnReceive(const uint8_t* data, size_t len,
-                                 bool is_binary) {
-  auto observer = observer_.lock();
-  if (observer) {
-    observer->OnReceive(data, len, is_binary);
-  }
-}
-void WsConnectionImpl::OnOpen() {
-  auto observer = observer_.lock();
-  if (observer) {
-    observer->OnOpen();
-  }
-}
-void WsConnectionImpl::OnClose() {
-  auto observer = observer_.lock();
-  if (observer) {
-    observer->OnClose();
-  }
-}
-
-void WsConnectionImpl::OnWriteable() {
-  WsBuffer buffer;
-  {
-    std::lock_guard<std::mutex> lock(write_queue_mutex_);
-    if (write_queue_.size() == 0) {
-      return;
-    }
-    buffer = std::move(write_queue_.front());
-    write_queue_.pop_front();
-  }
-  auto flags = lws_write_ws_flags(
-      buffer.is_binary() ? LWS_WRITE_BINARY : LWS_WRITE_TEXT, true, true);
-  auto res = lws_write(wsi_, buffer.data(), buffer.size(),
-                       (enum lws_write_protocol)flags);
-  if (res != buffer.size()) {
-    LOG(WARNING) << "Unable to send the entire message!";
-  }
-}
-
-bool WsConnectionImpl::Send(const uint8_t* data, size_t len, bool binary) {
-  if (!wsi_) {
-    LOG(WARNING) << "Send called on an uninitialized connection!!";
-    return false;
-  }
-  WsBuffer buffer(data, len, binary);
-  {
-    std::lock_guard<std::mutex> lock(write_queue_mutex_);
-    write_queue_.emplace_back(std::move(buffer));
-  }
-
-  lws_callback_on_writable(wsi_);
-  return true;
-}
-
-int LwsCallback(struct lws* wsi, enum lws_callback_reasons reason, void* user,
-                void* in, size_t len) {
-  constexpr int DROP = -1;
-  constexpr int OK = 0;
-
-  // For some values of `reason`, `user` doesn't point to the value provided
-  // when the connection was created. This function object should be used with
-  // care.
-  auto with_connection =
-      [wsi, user](std::function<void(std::shared_ptr<WsConnectionImpl>)> cb) {
-        auto context = reinterpret_cast<WsConnectionContextImpl*>(user);
-        auto connection = context->GetConnection(wsi);
-        if (!connection) {
-          return DROP;
-        }
-        cb(connection);
-        return OK;
-      };
-
-  switch (reason) {
-    case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
-      return with_connection(
-          [in](std::shared_ptr<WsConnectionImpl> connection) {
-            connection->OnError(in ? (char*)in : "(null)");
-          });
-
-    case LWS_CALLBACK_CLIENT_RECEIVE:
-      return with_connection(
-          [in, len, wsi](std::shared_ptr<WsConnectionImpl> connection) {
-            connection->OnReceive((const uint8_t*)in, len,
-                                  lws_frame_is_binary(wsi));
-          });
-
-    case LWS_CALLBACK_CLIENT_ESTABLISHED:
-      return with_connection([](std::shared_ptr<WsConnectionImpl> connection) {
-        connection->OnOpen();
-      });
-
-    case LWS_CALLBACK_CLIENT_CLOSED:
-      return with_connection([](std::shared_ptr<WsConnectionImpl> connection) {
-        connection->OnClose();
-      });
-
-    case LWS_CALLBACK_CLIENT_WRITEABLE:
-      return with_connection([](std::shared_ptr<WsConnectionImpl> connection) {
-        connection->OnWriteable();
-      });
-
-    case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
-      return with_connection(
-          [in, len](std::shared_ptr<WsConnectionImpl> connection) {
-            auto p = reinterpret_cast<unsigned char**>(in);
-            auto end = (*p) + len;
-            connection->AddHttpHeaders(p, end);
-          });
-
-    case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
-      // This callback is only called when we add additional HTTP headers, let
-      // LWS know we're done modifying the HTTP request.
-      lws_client_http_body_pending(wsi, 0);
-      return 0;
-
-    default:
-      LOG(VERBOSE) << "Unhandled value: " << reason;
-      return lws_callback_http_dummy(wsi, reason, user, in, len);
-  }
-}
-
-void CreateConnectionCallback(lws_sorted_usec_list_t* sul) {
-  std::shared_ptr<WsConnectionImpl> connection =
-      reinterpret_cast<WsConnectionImpl::CreateConnectionSul*>(sul)
-          ->weak_this.lock();
-  if (!connection) {
-    LOG(WARNING) << "The object was already destroyed by the time of the first "
-                 << "connection attempt. That's unusual.";
-    return;
-  }
-  connection->ConnectInner();
-}
-
-void WsConnectionImpl::ConnectInner() {
-  struct lws_client_connect_info connect_info;
-
-  memset(&connect_info, 0, sizeof(connect_info));
-
-  connect_info.context = context_->lws_context();
-  connect_info.port = port_;
-  connect_info.address = addr_.c_str();
-  connect_info.path = path_.c_str();
-  connect_info.host = connect_info.address;
-  connect_info.origin = connect_info.address;
-  switch (security_) {
-    case Security::kAllowSelfSigned:
-      connect_info.ssl_connection = LCCSCF_ALLOW_SELFSIGNED |
-                                    LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK |
-                                    LCCSCF_USE_SSL;
-      break;
-    case Security::kStrict:
-      connect_info.ssl_connection = LCCSCF_USE_SSL;
-      break;
-    case Security::kInsecure:
-      connect_info.ssl_connection = 0;
-      break;
-  }
-  connect_info.protocol = "webrtc-operator";
-  connect_info.local_protocol_name = kProtocolName;
-  connect_info.pwsi = &wsi_;
-  connect_info.retry_and_idle_policy = &kRetry;
-  // There is no guarantee the connection object still exists when the callback
-  // is called. Put the context instead as the user data which is guaranteed to
-  // still exist and holds a weak ptr to the connection.
-  connect_info.userdata = context_.get();
-
-  if (lws_client_connect_via_info(&connect_info)) {
-    // wsi_ is not initialized until after the call to
-    // lws_client_connect_via_info(). Luckily, this is guaranteed to run before
-    // the protocol callback is called because it runs in the same loop.
-    context_->RememberConnection(wsi_, weak_from_this());
-  } else {
-    LOG(ERROR) << "Connection failed!";
-  }
-}
diff --git a/host/frontend/webrtc/lib/ws_connection.h b/host/frontend/webrtc/lib/ws_connection.h
deleted file mode 100644
index 1c03265..0000000
--- a/host/frontend/webrtc/lib/ws_connection.h
+++ /dev/null
@@ -1,72 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-//
-
-#pragma once
-
-#include <string.h>
-
-#include <deque>
-#include <functional>
-#include <map>
-#include <memory>
-#include <mutex>
-#include <string>
-#include <thread>
-#include <vector>
-
-#include <libwebsockets.h>
-
-class WsConnectionObserver {
- public:
-  virtual ~WsConnectionObserver() = default;
-  virtual void OnOpen() = 0;
-  virtual void OnClose() = 0;
-  virtual void OnError(const std::string& error) = 0;
-  virtual void OnReceive(const uint8_t* msg, size_t length, bool is_binary) = 0;
-};
-
-class WsConnection {
- public:
-  enum class Security {
-    kInsecure,
-    kAllowSelfSigned,
-    kStrict,
-  };
-
-  virtual ~WsConnection() = default;
-
-  virtual void Connect() = 0;
-
-  virtual bool Send(const uint8_t* data, size_t len, bool binary = false) = 0;
-
- protected:
-  WsConnection() = default;
-};
-
-class WsConnectionContext {
- public:
-  static std::shared_ptr<WsConnectionContext> Create();
-
-  virtual ~WsConnectionContext() = default;
-
-  virtual std::shared_ptr<WsConnection> CreateConnection(
-      int port, const std::string& addr, const std::string& path,
-      WsConnection::Security secure,
-      std::weak_ptr<WsConnectionObserver> observer,
-      const std::vector<std::pair<std::string, std::string>>& headers = {}) = 0;
-
- protected:
-  WsConnectionContext() = default;
-};
diff --git a/host/frontend/webrtc/main.cpp b/host/frontend/webrtc/main.cpp
index 57d5644..b607dca 100644
--- a/host/frontend/webrtc/main.cpp
+++ b/host/frontend/webrtc/main.cpp
@@ -30,6 +30,7 @@
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/files.h"
 #include "host/frontend/webrtc/audio_handler.h"
+#include "host/frontend/webrtc/client_server.h"
 #include "host/frontend/webrtc/connection_observer.h"
 #include "host/frontend/webrtc/display_handler.h"
 #include "host/frontend/webrtc/kernel_log_events_handler.h"
@@ -59,6 +60,7 @@
             "Whether to send input events in virtio format.");
 DEFINE_int32(audio_server_fd, -1, "An fd to listen on for audio frames");
 DEFINE_int32(camera_streamer_fd, -1, "An fd to send client camera frames");
+DEFINE_string(client_dir, "webrtc", "Location of the client files");
 
 using cuttlefish::AudioHandler;
 using cuttlefish::CfConnectionObserverFactory;
@@ -68,6 +70,7 @@
 using cuttlefish::webrtc_streaming::Streamer;
 using cuttlefish::webrtc_streaming::StreamerConfig;
 using cuttlefish::webrtc_streaming::VideoSink;
+using cuttlefish::webrtc_streaming::ServerConfig;
 
 class CfOperatorObserver
     : public cuttlefish::webrtc_streaming::OperatorObserver {
@@ -131,6 +134,12 @@
   return std::make_unique<cuttlefish::AudioServer>(audio_server_fd);
 }
 
+fruit::Component<cuttlefish::CustomActionConfigProvider> WebRtcComponent() {
+  return fruit::createComponent()
+      .install(cuttlefish::ConfigFlagPlaceholder)
+      .install(cuttlefish::CustomActionsComponent);
+};
+
 int main(int argc, char** argv) {
   cuttlefish::DefaultSubprocessLogging(argv);
   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
@@ -197,6 +206,8 @@
   auto screen_connector_ptr = cuttlefish::DisplayHandler::ScreenConnector::Get(
       FLAGS_frame_server_fd, host_mode_ctrl);
   auto& screen_connector = *(screen_connector_ptr.get());
+  auto client_server = cuttlefish::ClientFilesServer::New(FLAGS_client_dir);
+  CHECK(client_server) << "Failed to initialize client files server";
 
   // create confirmation UI service, giving host_mode_ctrl and
   // screen_connector
@@ -207,15 +218,21 @@
   StreamerConfig streamer_config;
 
   streamer_config.device_id = instance.webrtc_device_id();
+  streamer_config.client_files_port = client_server->port();
   streamer_config.tcp_port_range = cvd_config->webrtc_tcp_port_range();
   streamer_config.udp_port_range = cvd_config->webrtc_udp_port_range();
   streamer_config.operator_server.addr = cvd_config->sig_server_address();
   streamer_config.operator_server.port = cvd_config->sig_server_port();
   streamer_config.operator_server.path = cvd_config->sig_server_path();
-  streamer_config.operator_server.security =
-      cvd_config->sig_server_strict()
-          ? WsConnection::Security::kStrict
-          : WsConnection::Security::kAllowSelfSigned;
+  if (cvd_config->sig_server_secure()) {
+    streamer_config.operator_server.security =
+        cvd_config->sig_server_strict()
+            ? ServerConfig::Security::kStrict
+            : ServerConfig::Security::kAllowSelfSigned;
+  } else {
+    streamer_config.operator_server.security =
+        ServerConfig::Security::kInsecure;
+  }
 
   if (!cvd_config->sig_server_headers_path().empty()) {
     streamer_config.operator_server.http_headers =
@@ -265,7 +282,6 @@
     CHECK(local_recorder) << "Could not create local recorder";
 
     streamer->RecordDisplays(*local_recorder);
-    display_handler->IncClientCount();
   }
 
   observer_factory->SetDisplayHandler(display_handler);
@@ -310,7 +326,17 @@
     action_server_fds[server] = fd;
   }
 
-  for (const auto& custom_action : cvd_config->custom_actions()) {
+  fruit::Injector<cuttlefish::CustomActionConfigProvider> injector(
+      WebRtcComponent);
+  for (auto& fragment :
+       injector.getMultibindings<cuttlefish::ConfigFragment>()) {
+    CHECK(cvd_config->LoadFragment(*fragment))
+        << "Failed to load config fragment";
+  }
+
+  const auto& actions_provider =
+      injector.get<cuttlefish::CustomActionConfigProvider&>();
+  for (const auto& custom_action : actions_provider.CustomActions()) {
     if (custom_action.shell_command) {
       if (custom_action.buttons.size() != 1) {
         LOG(FATAL) << "Expected exactly one button for custom action command: "
diff --git a/host/frontend/webrtc_operator/Android.bp b/host/frontend/webrtc_operator/Android.bp
index 54eda44..c734ae0 100644
--- a/host/frontend/webrtc_operator/Android.bp
+++ b/host/frontend/webrtc_operator/Android.bp
@@ -38,6 +38,7 @@
         "webrtc_signaling_headers",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "liblog",
         "libcrypto",
@@ -67,51 +68,23 @@
 }
 
 prebuilt_usr_share_host {
-    name: "webrtc_style.css",
-    src: "assets/style.css",
-    filename: "style.css",
+    name: "webrtc_index.css",
+    src: "assets/index.css",
+    filename: "index.css",
     sub_dir: "webrtc/assets",
 }
 
 prebuilt_usr_share_host {
-    name: "webrtc_controls.css",
-    src: "assets/controls.css",
-    filename: "controls.css",
-    sub_dir: "webrtc/assets",
-}
-
-prebuilt_usr_share_host {
-    name: "webrtc_adb.js",
-    src: "assets/js/adb.js",
-    filename: "adb.js",
+    name: "webrtc_index.js",
+    src: "assets/js/index.js",
+    filename: "index.js",
     sub_dir: "webrtc/assets/js",
 }
 
 prebuilt_usr_share_host {
-    name: "webrtc_cf.js",
-    src: "assets/js/cf_webrtc.js",
-    filename: "cf_webrtc.js",
-    sub_dir: "webrtc/assets/js",
-}
-
-prebuilt_usr_share_host {
-    name: "webrtc_app.js",
-    src: "assets/js/app.js",
-    filename: "app.js",
-    sub_dir: "webrtc/assets/js",
-}
-
-prebuilt_usr_share_host {
-    name: "webrtc_controls.js",
-    src: "assets/js/controls.js",
-    filename: "controls.js",
-    sub_dir: "webrtc/assets/js",
-}
-
-prebuilt_usr_share_host {
-    name: "webrtc_rootcanal.js",
-    src: "assets/js/rootcanal.js",
-    filename: "rootcanal.js",
+    name: "webrtc_server_connector.js",
+    src: "assets/js/server_connector.js",
+    filename: "server_connector.js",
     sub_dir: "webrtc/assets/js",
 }
 
diff --git a/common/libs/utils/size_utils.cpp b/host/frontend/webrtc_operator/assets/index.css
similarity index 64%
copy from common/libs/utils/size_utils.cpp
copy to host/frontend/webrtc_operator/assets/index.css
index 9f25445..335e075 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/frontend/webrtc_operator/assets/index.css
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,24 @@
  * limitations under the License.
  */
 
-#include "common/libs/utils/size_utils.h"
-
-#include <unistd.h>
-
-namespace cuttlefish {
-
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
+body {
+  background-color:black;
+  margin: 0;
 }
 
-}  // namespace cuttlefish
+#device-selector {
+  color: whitesmoke;
+}
+
+#device-selector li.device-entry {
+  cursor: pointer;
+}
+
+#refresh-list {
+  cursor: pointer;
+}
+
+#device-list .device-entry button {
+  margin-left: 10px;
+}
+
diff --git a/host/frontend/webrtc_operator/assets/index.html b/host/frontend/webrtc_operator/assets/index.html
index f0dc25e..09d8c70 100644
--- a/host/frontend/webrtc_operator/assets/index.html
+++ b/host/frontend/webrtc_operator/assets/index.html
@@ -18,11 +18,8 @@
     <head>
         <title>My Virtual Device Playground</title>
 
-        <link rel="stylesheet" type="text/css" href="style.css" >
-        <link rel="stylesheet" type="text/css" href="controls.css" >
-        <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined">
+        <link rel="stylesheet" type="text/css" href="index.css" >
         <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
-        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
     </head>
 
     <body>
@@ -30,57 +27,6 @@
         <h1>Available devices <span id='refresh-list'>&#8635;</span></h1>
         <ul id="device-list"></ul>
       </section>
-      <section id='device-connection'>
-        <div id='header'>
-          <div id='app-controls'>
-            <div id="keyboard-capture-control" title="Capture Keyboard"></div>
-            <div id="mic-capture-control" title="Capture Microphone"></div>
-            <div id="camera-control" title="Capture Camera"></div>
-            <audio autoplay controls id="device-audio"></audio>
-          </div>
-          <div id='status-div'>
-            <h3 id='status-message' class='connecting'>Connecting to device</h3>
-          </div>
-        </div>
-        <div id='controls-and-displays'>
-          <div id='control-panel-default-buttons' class='control-panel-column'>
-            <button id='device-details-button' title='Device Details' class='material-icons'>
-              settings
-            </button>
-            <button id='bluetooth-console-button' title='Bluetooth console' class='material-icons'>
-              settings_bluetooth
-            </button>
-          </div>
-          <div id='control-panel-custom-buttons' class='control-panel-column'></div>
-          <div id='device-displays'>
-          </div>
-        </div>
-      </section>
-      <div id='device-details-modal' class='modal'>
-        <div id='device-details-modal-header' class='modal-header'>
-          <h2>Device Details</h2>
-          <button id='device-details-close' title='Close' class='material-icons modal-close'>close</button>
-        </div>
-        <hr>
-        <h3>Hardware Configuration</h3>
-        <span id='device-details-hardware'>unknown</span>
-      </div>
-      <div id='bluetooth-console-modal' class='modal'>
-        <div id='bluetooth-console-modal-header' class='modal-header'>
-          <h2>Bluetooth Console</h2>
-          <button id='bluetooth-console-close' title='Close' class='material-icons modal-close'>close</button>
-        </div>
-        <div>
-          <table>
-            <tr><td colspan='2'><textarea id='bluetooth-console-view' readonly rows='10' cols='60'></textarea></td></tr>
-            <tr><td width='1'><p id='bluetooth-console-cmd-label'>Command:</p></td><td width='100'><input id='bluetooth-console-input' type='text'></input></td></tr>
-          </table>
-        </div>
-      </div>
-       <script src="js/adb.js"></script>
-       <script src="js/rootcanal.js"></script>
-       <script src="js/cf_webrtc.js" type="module"></script>
-       <script src="js/controls.js"></script>
-       <script src="js/app.js"></script>
+      <script src="js/index.js"></script>
     </body>
 </html>
diff --git a/host/frontend/webrtc_operator/assets/js/adb.js b/host/frontend/webrtc_operator/assets/js/adb.js
deleted file mode 100644
index 75540ae..0000000
--- a/host/frontend/webrtc_operator/assets/js/adb.js
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-let adb_ws;
-
-let utf8Encoder = new TextEncoder();
-let utf8Decoder = new TextDecoder();
-
-const A_CNXN = 0x4e584e43;
-const A_OPEN = 0x4e45504f;
-const A_WRTE = 0x45545257;
-const A_OKAY = 0x59414b4f;
-
-const kLocalChannelId = 666;
-
-let array = new Uint8Array();
-
-function setU32LE(array, offset, x) {
-    array[offset] = x & 0xff;
-    array[offset + 1] = (x >> 8) & 0xff;
-    array[offset + 2] = (x >> 16) & 0xff;
-    array[offset + 3] = x >> 24;
-}
-
-function getU32LE(array, offset) {
-    let x = array[offset]
-        | (array[offset + 1] << 8)
-        | (array[offset + 2] << 16)
-        | (array[offset + 3] << 24);
-
-    return x >>> 0;  // convert signed to unsigned if necessary.
-}
-
-function computeChecksum(array) {
-    let sum = 0;
-    let i;
-    for (i = 0; i < array.length; ++i) {
-        sum = ((sum + array[i]) & 0xffffffff) >>> 0;
-    }
-
-    return sum;
-}
-
-function createAdbMessage(command, arg0, arg1, payload) {
-    let arrayBuffer = new ArrayBuffer(24 + payload.length);
-    let array = new Uint8Array(arrayBuffer);
-    setU32LE(array, 0, command);
-    setU32LE(array, 4, arg0);
-    setU32LE(array, 8, arg1);
-    setU32LE(array, 12, payload.length);
-    setU32LE(array, 16, computeChecksum(payload));
-    setU32LE(array, 20, command ^ 0xffffffff);
-    array.set(payload, 24);
-
-    return arrayBuffer;
-}
-
-function adbOpenConnection() {
-    let systemIdentity = utf8Encoder.encode("Cray_II:1234:whatever");
-
-    let arrayBuffer = createAdbMessage(
-        A_CNXN, 0x1000000, 256 * 1024, systemIdentity);
-
-    adb_ws.send(arrayBuffer);
-}
-
-function adbShell(command) {
-    let destination = utf8Encoder.encode("shell:" + command);
-
-    let arrayBuffer = createAdbMessage(A_OPEN, kLocalChannelId, 0, destination);
-    adb_ws.send(arrayBuffer);
-    awaitConnection();
-}
-
-function adbSendOkay(remoteId) {
-    let payload = new Uint8Array(0);
-
-    let arrayBuffer = createAdbMessage(
-        A_OKAY, kLocalChannelId, remoteId, payload);
-
-    adb_ws.send(arrayBuffer);
-}
-
-function JoinArrays(arr1, arr2) {
-  let arr = new Uint8Array(arr1.length + arr2.length);
-  arr.set(arr1, 0);
-  arr.set(arr2, arr1.length);
-  return arr;
-}
-
-// Simple lifecycle management that executes callbacks based on connection state.
-//
-// Any attempt to initiate a command (e.g. creating a connection, sending a message)
-// (re)starts a timer. Any response back from any command stops that timer.
-const timeoutMs = 3000;
-let connectedCb;
-let disconnectedCb;
-let disconnectedTimeout;
-function awaitConnection() {
-  clearTimeout(disconnectedTimeout);
-  if (disconnectedCb) {
-    disconnectedTimeout = setTimeout(disconnectedCb, timeoutMs);
-  }
-}
-function connected() {
-  if (disconnectedTimeout) {
-    clearTimeout(disconnectedTimeout);
-  }
-  if (connectedCb) {
-    connectedCb();
-  }
-}
-
-function adbOnMessage(arrayBuffer) {
-    // console.log("adb_ws: onmessage (" + arrayBuffer.byteLength + " bytes)");
-    array = JoinArrays(array, new Uint8Array(arrayBuffer));
-
-    while (array.length > 0) {
-        if (array.length < 24) {
-            // Incomplete package, must wait for more data.
-            return;
-        }
-
-        let command = getU32LE(array, 0);
-        let magic = getU32LE(array, 20);
-
-        if (command != ((magic ^ 0xffffffff) >>> 0)) {
-            console.log("command = " + command + ", magic = " + magic);
-            console.log("adb message command vs magic failed.");
-            return;
-        }
-
-        let payloadLength = getU32LE(array, 12);
-
-        if (array.length < 24 + payloadLength) {
-            // Incomplete package, must wait for more data.
-            return;
-        }
-
-        let payloadChecksum = getU32LE(array, 16);
-        let checksum = computeChecksum(array.slice(24));
-
-        if (payloadChecksum != checksum) {
-            console.log("adb message checksum mismatch.");
-            // This can happen if a shell command executes while another
-            // channel is receiving data.
-        }
-
-        switch (command) {
-            case A_CNXN:
-            {
-                console.log("WebRTC adb connected.");
-                connected();
-                break;
-            }
-
-            case A_OKAY:
-            {
-                let remoteId = getU32LE(array, 4);
-                console.log("WebRTC adb channel created w/ remoteId " + remoteId);
-                connected();
-                break;
-            }
-
-            case A_WRTE:
-            {
-                let remoteId = getU32LE(array, 4);
-                adbSendOkay(remoteId);
-                break;
-            }
-        }
-        array = array.subarray(24 + payloadLength, array.length);
-    }
-}
-
-function init_adb(devConn, ccb = connectedCb, dcb = disconnectedCb) {
-    if (adb_ws) return;
-
-    adb_ws = {
-      send: function(buffer) {
-        devConn.sendAdbMessage(buffer);
-      }
-    };
-    connectedCb = ccb;
-    disconnectedCb = dcb;
-    awaitConnection();
-
-    devConn.onAdbMessage(msg => adbOnMessage(msg));
-
-    adbOpenConnection();
-}
diff --git a/host/frontend/webrtc_operator/assets/js/app.js b/host/frontend/webrtc_operator/assets/js/app.js
deleted file mode 100644
index db9c8a7..0000000
--- a/host/frontend/webrtc_operator/assets/js/app.js
+++ /dev/null
@@ -1,972 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-'use strict';
-
-function ConnectToDevice(device_id) {
-  console.log('ConnectToDevice ', device_id);
-  const keyboardCaptureCtrl = document.getElementById('keyboard-capture-control');
-  createToggleControl(keyboardCaptureCtrl, "keyboard", onKeyboardCaptureToggle);
-  const micCaptureCtrl = document.getElementById('mic-capture-control');
-  createToggleControl(micCaptureCtrl, "mic", onMicCaptureToggle);
-  const cameraCtrl = document.getElementById('camera-control');
-  createToggleControl(cameraCtrl, "videocam", onVideoCaptureToggle);
-
-  const deviceAudio = document.getElementById('device-audio');
-  const deviceDisplays = document.getElementById('device-displays');
-  const statusMessage = document.getElementById('status-message');
-
-  let connectionAttemptDuration = 0;
-  const intervalMs = 500;
-  let deviceStatusEllipsisCount = 0;
-  let animateDeviceStatusMessage = setInterval(function() {
-    connectionAttemptDuration += intervalMs;
-    if (connectionAttemptDuration > 30000) {
-      statusMessage.className = 'error';
-      statusMessage.textContent = 'Connection should have occurred by now. ' +
-          'Please attempt to restart the guest device.';
-    } else {
-      if (connectionAttemptDuration > 15000) {
-        statusMessage.textContent = 'Connection is taking longer than expected';
-      } else {
-        statusMessage.textContent = 'Connecting to device';
-      }
-      deviceStatusEllipsisCount = (deviceStatusEllipsisCount + 1) % 4;
-      statusMessage.textContent += '.'.repeat(deviceStatusEllipsisCount);
-    }
-  }, intervalMs);
-
-  let buttons = {};
-  let mouseIsDown = false;
-  let deviceConnection;
-  let touchIdSlotMap = new Map();
-  let touchSlots = new Array();
-  let deviceStateLidSwitchOpen = null;
-  let deviceStateHingeAngleValue = null;
-
-  function showAdbConnected() {
-    // Screen changed messages are not reported until after boot has completed.
-    // Certain default adb buttons change screen state, so wait for boot
-    // completion before enabling these buttons.
-    statusMessage.className = 'connected';
-    statusMessage.textContent =
-        'adb connection established successfully.';
-    setTimeout(function() {
-      statusMessage.style.visibility = 'hidden';
-    }, 5000);
-    for (const [_, button] of Object.entries(buttons)) {
-      if (button.adb) {
-        button.button.disabled = false;
-      }
-    }
-  }
-
-  function initializeAdb() {
-    init_adb(
-        deviceConnection,
-        showAdbConnected,
-        function() {
-          statusMessage.className = 'error';
-          statusMessage.textContent = 'adb connection failed.';
-          statusMessage.style.visibility = 'visible';
-          for (const [_, button] of Object.entries(buttons)) {
-            if (button.adb) {
-              button.button.disabled = true;
-            }
-          }
-        });
-  }
-
-  let currentRotation = 0;
-  let currentDisplayDescriptions;
-  function onControlMessage(message) {
-    let message_data = JSON.parse(message.data);
-    console.log(message_data)
-    let metadata = message_data.metadata;
-    if (message_data.event == 'VIRTUAL_DEVICE_BOOT_STARTED') {
-      // Start the adb connection after receiving the BOOT_STARTED message.
-      // (This is after the adbd start message. Attempting to connect
-      // immediately after adbd starts causes issues.)
-      initializeAdb();
-    }
-    if (message_data.event == 'VIRTUAL_DEVICE_SCREEN_CHANGED') {
-      if (metadata.rotation != currentRotation) {
-        // Animate the screen rotation.
-        const targetRotation = metadata.rotation == 0 ? 0 : -90;
-
-        $(deviceDisplays).animate(
-          {
-            textIndent: targetRotation,
-          },
-          {
-            duration: 1000,
-            step: function(now, tween) {
-              resizeDeviceDisplays();
-            },
-          }
-        );
-      }
-
-      currentRotation = metadata.rotation;
-    }
-    if (message_data.event == 'VIRTUAL_DEVICE_CAPTURE_IMAGE') {
-      if (deviceConnection.cameraEnabled) {
-        takePhoto();
-      }
-    }
-    if (message_data.event == 'VIRTUAL_DEVICE_DISPLAY_POWER_MODE_CHANGED') {
-      updateDisplayVisibility(metadata.display, metadata.mode);
-    }
-  }
-
-  function updateDisplayVisibility(displayId, powerMode) {
-    const display = document.getElementById('display_' + displayId).parentElement;
-    if (display == null) {
-      console.error('Unknown display id: ' + displayId);
-      return;
-    }
-    switch (powerMode) {
-      case 'On':
-        display.style.visibility = 'visible';
-        break;
-      case 'Off':
-        display.style.visibility = 'hidden';
-        break;
-      default:
-        console.error('Display ' + displayId + ' has unknown display power mode: ' + powerMode);
-    }
-  }
-
-  function getTransformRotation(element) {
-    if (!element.style.textIndent) {
-      return 0;
-    }
-    // Remove 'px' and convert to float.
-    return parseFloat(element.style.textIndent.slice(0, -2));
-  }
-
-  let anyDeviceDisplayLoaded = false;
-  function onDeviceDisplayLoaded() {
-    if (anyDeviceDisplayLoaded) {
-      return;
-    }
-    anyDeviceDisplayLoaded = true;
-
-    clearInterval(animateDeviceStatusMessage);
-    statusMessage.textContent = 'Awaiting bootup and adb connection. Please wait...';
-    resizeDeviceDisplays();
-
-    let deviceDisplayList =
-      document.getElementsByClassName("device-display");
-    for (const deviceDisplay of deviceDisplayList) {
-      deviceDisplay.style.visibility = 'visible';
-    }
-
-    // Enable the buttons after the screen is visible.
-    for (const [_, button] of Object.entries(buttons)) {
-      if (!button.adb) {
-        button.button.disabled = false;
-      }
-    }
-    // Start the adb connection if it is not already started.
-    initializeAdb();
-  }
-
-  // Creates a <video> element and a <div> container element for each display.
-  // The extra <div> container elements are used to maintain the width and
-  // height of the device as the CSS 'transform' property used on the <video>
-  // element for rotating the device only affects the visuals of the element
-  // and not its layout.
-  function createDeviceDisplays(devConn) {
-    for (const deviceDisplayDescription of currentDisplayDescriptions) {
-      let deviceDisplay = document.createElement("div");
-      deviceDisplay.classList.add("device-display");
-      // Start the screen as hidden. Only show when data is ready.
-      deviceDisplay.style.visibility = 'hidden';
-
-      let deviceDisplayInfo = document.createElement("div");
-      deviceDisplayInfo.classList.add("device-display-info");
-      deviceDisplayInfo.id = deviceDisplayDescription.stream_id + '_info';
-      deviceDisplay.appendChild(deviceDisplayInfo);
-
-      let deviceDisplayVideo = document.createElement("video");
-      deviceDisplayVideo.autoplay = true;
-      deviceDisplayVideo.id = deviceDisplayDescription.stream_id;
-      deviceDisplayVideo.classList.add("device-display-video");
-      deviceDisplayVideo.addEventListener('loadeddata', (evt) => {
-        onDeviceDisplayLoaded();
-      });
-      deviceDisplay.appendChild(deviceDisplayVideo);
-
-      deviceDisplays.appendChild(deviceDisplay);
-
-      let stream_id = deviceDisplayDescription.stream_id;
-      devConn.getStream(stream_id).then(stream => {
-        deviceDisplayVideo.srcObject = stream;
-      }).catch(e => console.error('Unable to get display stream: ', e));
-    }
-  }
-
-  function takePhoto() {
-    const imageCapture = deviceConnection.imageCapture;
-    if (imageCapture) {
-      const photoSettings = {
-        imageWidth: deviceConnection.cameraWidth,
-        imageHeight: deviceConnection.cameraHeight
-      }
-      imageCapture.takePhoto(photoSettings)
-        .then(blob => blob.arrayBuffer())
-        .then(buffer => deviceConnection.sendOrQueueCameraData(buffer))
-        .catch(error => console.log(error));
-    }
-  }
-
-  function resizeDeviceDisplays() {
-    // Padding between displays.
-    const deviceDisplayWidthPadding = 10;
-    // Padding for the display info above each display video.
-    const deviceDisplayHeightPadding = 38;
-
-    let deviceDisplayList =
-      document.getElementsByClassName("device-display");
-    let deviceDisplayVideoList =
-      document.getElementsByClassName("device-display-video");
-    let deviceDisplayInfoList =
-      document.getElementsByClassName("device-display-info");
-
-    const rotationDegrees = getTransformRotation(deviceDisplays);
-    const rotationRadians = rotationDegrees * Math.PI / 180;
-
-    // Auto-scale the screen based on window size.
-    let availableWidth = deviceDisplays.clientWidth;
-    let availableHeight = deviceDisplays.clientHeight - deviceDisplayHeightPadding;
-
-    // Reserve space for padding between the displays.
-    availableWidth = availableWidth -
-      (currentDisplayDescriptions.length * deviceDisplayWidthPadding);
-
-    // Loop once over all of the displays to compute the total space needed.
-    let neededWidth = 0;
-    let neededHeight = 0;
-    for (let i = 0; i < deviceDisplayList.length; i++) {
-      let deviceDisplayDescription = currentDisplayDescriptions[i];
-      let deviceDisplayVideo = deviceDisplayVideoList[i];
-
-      const originalDisplayWidth = deviceDisplayDescription.x_res;
-      const originalDisplayHeight = deviceDisplayDescription.y_res;
-
-      const neededBoundingBoxWidth =
-        Math.abs(Math.cos(rotationRadians) * originalDisplayWidth) +
-        Math.abs(Math.sin(rotationRadians) * originalDisplayHeight);
-      const neededBoundingBoxHeight =
-        Math.abs(Math.sin(rotationRadians) * originalDisplayWidth) +
-        Math.abs(Math.cos(rotationRadians) * originalDisplayHeight);
-
-      neededWidth = neededWidth + neededBoundingBoxWidth;
-      neededHeight = Math.max(neededHeight, neededBoundingBoxHeight);
-    }
-
-    const scaling = Math.min(availableWidth / neededWidth,
-                             availableHeight / neededHeight);
-
-    // Loop again over all of the displays to set the sizes and positions.
-    let deviceDisplayLeftOffset = 0;
-    for (let i = 0; i < deviceDisplayList.length; i++) {
-      let deviceDisplay = deviceDisplayList[i];
-      let deviceDisplayVideo = deviceDisplayVideoList[i];
-      let deviceDisplayInfo = deviceDisplayInfoList[i];
-      let deviceDisplayDescription = currentDisplayDescriptions[i];
-
-      let rotated = currentRotation == 1 ? ' (Rotated)' : '';
-      deviceDisplayInfo.textContent = `Display ${i} - ` +
-          `${deviceDisplayDescription.x_res}x` +
-          `${deviceDisplayDescription.y_res} ` +
-          `(${deviceDisplayDescription.dpi} DPI)${rotated}`;
-
-      const originalDisplayWidth = deviceDisplayDescription.x_res;
-      const originalDisplayHeight = deviceDisplayDescription.y_res;
-
-      const scaledDisplayWidth = originalDisplayWidth * scaling;
-      const scaledDisplayHeight = originalDisplayHeight * scaling;
-
-      const neededBoundingBoxWidth =
-        Math.abs(Math.cos(rotationRadians) * originalDisplayWidth) +
-        Math.abs(Math.sin(rotationRadians) * originalDisplayHeight);
-      const neededBoundingBoxHeight =
-        Math.abs(Math.sin(rotationRadians) * originalDisplayWidth) +
-        Math.abs(Math.cos(rotationRadians) * originalDisplayHeight);
-
-      const scaledBoundingBoxWidth = neededBoundingBoxWidth * scaling;
-      const scaledBoundingBoxHeight = neededBoundingBoxHeight * scaling;
-
-      const offsetX = (scaledBoundingBoxWidth - scaledDisplayWidth) / 2;
-      const offsetY = (scaledBoundingBoxHeight - scaledDisplayHeight) / 2;
-
-      deviceDisplayVideo.style.width = scaledDisplayWidth;
-      deviceDisplayVideo.style.height = scaledDisplayHeight;
-      deviceDisplayVideo.style.transform =
-        `translateX(${offsetX}px) ` +
-        `translateY(${offsetY}px) ` +
-        `rotateZ(${rotationDegrees}deg) `;
-
-      deviceDisplay.style.left = `${deviceDisplayLeftOffset}px`;
-      deviceDisplay.style.width = scaledBoundingBoxWidth;
-      deviceDisplay.style.height = scaledBoundingBoxHeight;
-
-      deviceDisplayLeftOffset =
-        deviceDisplayLeftOffset +
-        deviceDisplayWidthPadding +
-        scaledBoundingBoxWidth;
-    }
-  }
-  window.onresize = resizeDeviceDisplays;
-
-  function createControlPanelButton(command, title, icon_name,
-      listener=onControlPanelButton,
-      parent_id='control-panel-default-buttons') {
-    let button = document.createElement('button');
-    document.getElementById(parent_id).appendChild(button);
-    button.title = title;
-    button.dataset.command = command;
-    button.disabled = true;
-    // Capture mousedown/up/out commands instead of click to enable
-    // hold detection. mouseout is used to catch if the user moves the
-    // mouse outside the button while holding down.
-    button.addEventListener('mousedown', listener);
-    button.addEventListener('mouseup', listener);
-    button.addEventListener('mouseout', listener);
-    // Set the button image using Material Design icons.
-    // See http://google.github.io/material-design-icons
-    // and https://material.io/resources/icons
-    button.classList.add('material-icons');
-    button.innerHTML = icon_name;
-    buttons[command] = { 'button': button }
-    return buttons[command];
-  }
-  createControlPanelButton('power', 'Power', 'power_settings_new');
-  createControlPanelButton('home', 'Home', 'home');
-  createControlPanelButton('menu', 'Menu', 'menu');
-  createControlPanelButton('rotate', 'Rotate', 'screen_rotation', onRotateButton);
-  buttons['rotate'].adb = true;
-  createControlPanelButton('volumemute', 'Volume Mute', 'volume_mute');
-  createControlPanelButton('volumedown', 'Volume Down', 'volume_down');
-  createControlPanelButton('volumeup', 'Volume Up', 'volume_up');
-
-  let modalOffsets = {}
-  function createModalButton(button_id, modal_id, close_id) {
-    const modalButton = document.getElementById(button_id);
-    const modalDiv = document.getElementById(modal_id);
-    const modalHeader = modalDiv.querySelector('.modal-header');
-    const modalClose = document.getElementById(close_id);
-
-    // Position the modal to the right of the show modal button.
-    modalDiv.style.top = modalButton.offsetTop;
-    modalDiv.style.left = modalButton.offsetWidth + 30;
-
-    function showHideModal(show) {
-      if (show) {
-        modalButton.classList.add('modal-button-opened')
-        modalDiv.style.display = 'block';
-      } else {
-        modalButton.classList.remove('modal-button-opened')
-        modalDiv.style.display = 'none';
-      }
-    }
-    // Allow the show modal button to toggle the modal,
-    modalButton.addEventListener('click',
-        evt => showHideModal(modalDiv.style.display != 'block'));
-    // but the close button always closes.
-    modalClose.addEventListener('click',
-        evt => showHideModal(false));
-
-    // Allow the modal to be dragged by the header.
-    modalOffsets[modal_id] = {
-      midDrag: false,
-      mouseDownOffsetX: null,
-      mouseDownOffsetY: null,
-    }
-    modalHeader.addEventListener('mousedown',
-        evt => {
-            modalOffsets[modal_id].midDrag = true;
-            // Store the offset of the mouse location from the
-            // modal's current location.
-            modalOffsets[modal_id].mouseDownOffsetX =
-                parseInt(modalDiv.style.left) - evt.clientX;
-            modalOffsets[modal_id].mouseDownOffsetY =
-                parseInt(modalDiv.style.top) - evt.clientY;
-        });
-    modalHeader.addEventListener('mousemove',
-        evt => {
-            let offsets = modalOffsets[modal_id];
-            if (offsets.midDrag) {
-              // Move the modal to the mouse location plus the
-              // offset calculated on the initial mouse-down.
-              modalDiv.style.left =
-                  evt.clientX + offsets.mouseDownOffsetX;
-              modalDiv.style.top =
-                  evt.clientY + offsets.mouseDownOffsetY;
-            }
-        });
-    document.addEventListener('mouseup',
-        evt => {
-          modalOffsets[modal_id].midDrag = false;
-        });
-  }
-
-  createModalButton(
-    'device-details-button', 'device-details-modal', 'device-details-close');
-  createModalButton(
-    'bluetooth-console-button', 'bluetooth-console-modal', 'bluetooth-console-close');
-
-  let options = {
-    wsUrl: ((location.protocol == 'http:') ? 'ws://' : 'wss://') +
-      location.host + '/connect_client',
-  };
-
-  function showWebrtcError() {
-    statusMessage.className = 'error';
-    statusMessage.textContent = 'No connection to the guest device. ' +
-        'Please ensure the WebRTC process on the host machine is active.';
-    statusMessage.style.visibility = 'visible';
-    deviceDisplays.style.display = 'none';
-    for (const [_, button] of Object.entries(buttons)) {
-      button.button.disabled = true;
-    }
-  }
-
-  import('./cf_webrtc.js')
-    .then(webrtcModule => webrtcModule.Connect(device_id, options))
-    .then(devConn => {
-      deviceConnection = devConn;
-
-      console.log(deviceConnection.description);
-
-      currentDisplayDescriptions = devConn.description.displays;
-
-      createDeviceDisplays(devConn);
-      for (const audio_desc of devConn.description.audio_streams) {
-        let stream_id = audio_desc.stream_id;
-        devConn.getStream(stream_id).then(stream => {
-          deviceAudio.srcObject = stream;
-        }).catch(e => console.error('Unable to get audio stream: ', e));
-      }
-      startMouseTracking();  // TODO stopMouseTracking() when disconnected
-      updateDeviceHardwareDetails(deviceConnection.description.hardware);
-      if (deviceConnection.description.custom_control_panel_buttons.length > 0) {
-        document.getElementById('control-panel-custom-buttons').style.display = 'flex';
-        for (const button of deviceConnection.description.custom_control_panel_buttons) {
-          if (button.shell_command) {
-            // This button's command is handled by sending an ADB shell command.
-            createControlPanelButton(button.command, button.title, button.icon_name,
-                e => onCustomShellButton(button.shell_command, e),
-                'control-panel-custom-buttons');
-            buttons[button.command].adb = true;
-          } else if (button.device_states) {
-            // This button corresponds to variable hardware device state(s).
-            createControlPanelButton(button.command, button.title, button.icon_name,
-                getCustomDeviceStateButtonCb(button.device_states),
-                'control-panel-custom-buttons');
-            for (const device_state of button.device_states) {
-              // hinge_angle is currently injected via an adb shell command that
-              // triggers a guest binary.
-              if ('hinge_angle_value' in device_state) {
-                buttons[button.command].adb = true;
-              }
-            }
-          } else {
-            // This button's command is handled by custom action server.
-            createControlPanelButton(button.command, button.title, button.icon_name,
-                onControlPanelButton,
-                'control-panel-custom-buttons');
-          }
-        }
-      }
-      deviceConnection.onControlMessage(msg => onControlMessage(msg));
-      // Show the error message and disable buttons when the WebRTC connection fails.
-      deviceConnection.onConnectionStateChange(state => {
-        if (state == 'disconnected' || state == 'failed') {
-          showWebrtcError();
-        }
-      });
-      deviceConnection.onBluetoothMessage(msg => {
-        bluetoothConsole.addLine(decodeRootcanalMessage(msg));
-      });
-  }, rejection => {
-      console.error('Unable to connect: ', rejection);
-      showWebrtcError();
-  });
-
-  let hardwareDetailsText = '';
-  let deviceStateDetailsText = '';
-  function updateDeviceDetailsText() {
-    document.getElementById('device-details-hardware').textContent = [
-      hardwareDetailsText,
-      deviceStateDetailsText,
-    ].filter(e => e /*remove empty*/).join('\n');
-  }
-  function updateDeviceHardwareDetails(hardware) {
-    let hardwareDetailsTextLines = [];
-    Object.keys(hardware).forEach(function(key) {
-      let value = hardware[key];
-      hardwareDetailsTextLines.push(`${key} - ${value}`);
-    });
-
-    hardwareDetailsText = hardwareDetailsTextLines.join('\n');
-    updateDeviceDetailsText();
-  }
-  function updateDeviceStateDetails() {
-    let deviceStateDetailsTextLines = [];
-    if (deviceStateLidSwitchOpen != null) {
-      let state = deviceStateLidSwitchOpen ? 'Opened' : 'Closed';
-      deviceStateDetailsTextLines.push(`Lid Switch - ${state}`);
-    }
-    if (deviceStateHingeAngleValue != null) {
-      deviceStateDetailsTextLines.push(`Hinge Angle - ${deviceStateHingeAngleValue}`);
-    }
-    deviceStateDetailsText = deviceStateDetailsTextLines.join('\n');
-    updateDeviceDetailsText();
-  }
-
-  function onKeyboardCaptureToggle(enabled) {
-    if (enabled) {
-      startKeyboardTracking();
-    } else {
-      stopKeyboardTracking();
-    }
-  }
-
-  function onMicCaptureToggle(enabled) {
-    deviceConnection.useMic(enabled);
-  }
-
-  function onVideoCaptureToggle(enabled) {
-    deviceConnection.useVideo(enabled);
-  }
-
-  function cmdConsole(consoleViewName, consoleInputName) {
-    let consoleView = document.getElementById(consoleViewName);
-
-    let addString = function(str) {
-      consoleView.value += str;
-      consoleView.scrollTop = consoleView.scrollHeight;
-    }
-
-    let addLine = function(line) {
-      addString(line + "\r\n");
-    }
-
-    let commandCallbacks = [];
-
-    let addCommandListener = function(f) {
-      commandCallbacks.push(f);
-    }
-
-    let onCommand = function(cmd) {
-      cmd = cmd.trim();
-
-      if (cmd.length == 0) return;
-
-      commandCallbacks.forEach(f => {
-        f(cmd);
-      })
-    }
-
-    addCommandListener(cmd => addLine(">> " + cmd));
-
-    let consoleInput = document.getElementById(consoleInputName);
-
-    consoleInput.addEventListener('keydown', e => {
-      if ((e.key && e.key == 'Enter') || e.keyCode == 13) {
-        let command = e.target.value;
-
-        e.target.value = '';
-
-        onCommand(command);
-      }
-    })
-
-    return {
-      consoleView: consoleView,
-      consoleInput: consoleInput,
-      addLine: addLine,
-      addString: addString,
-      addCommandListener: addCommandListener,
-    };
-  }
-
-  var bluetoothConsole = cmdConsole(
-    'bluetooth-console-view', 'bluetooth-console-input');
-
-  bluetoothConsole.addCommandListener(cmd => {
-    let inputArr = cmd.split(' ');
-    let command = inputArr[0];
-    inputArr.shift();
-    let args = inputArr;
-    deviceConnection.sendBluetoothMessage(createRootcanalMessage(command, args));
-  })
-
-  function onControlPanelButton(e) {
-    if (e.type == 'mouseout' && e.which == 0) {
-      // Ignore mouseout events if no mouse button is pressed.
-      return;
-    }
-    deviceConnection.sendControlMessage(JSON.stringify({
-      command: e.target.dataset.command,
-      button_state: e.type == 'mousedown' ? "down" : "up",
-    }));
-  }
-
-  function onRotateButton(e) {
-    // Attempt to init adb again, in case the initial connection failed.
-    // This succeeds immediately if already connected.
-    initializeAdb();
-    if (e.type == 'mousedown') {
-      adbShell(
-          '/vendor/bin/cuttlefish_sensor_injection rotate ' +
-          (currentRotation == 0 ? 'landscape' : 'portrait'))
-    }
-  }
-
-  function onCustomShellButton(shell_command, e) {
-    // Attempt to init adb again, in case the initial connection failed.
-    // This succeeds immediately if already connected.
-    initializeAdb();
-    if (e.type == 'mousedown') {
-      adbShell(shell_command);
-    }
-  }
-
-  function getCustomDeviceStateButtonCb(device_states) {
-    let states = device_states;
-    let index = 0;
-    return e => {
-      if (e.type == 'mousedown') {
-        // Reset any overridden device state.
-        adbShell('cmd device_state state reset');
-        // Send a device_state message for the current state.
-        let message = {
-          command: 'device_state',
-          ...states[index],
-        };
-        deviceConnection.sendControlMessage(JSON.stringify(message));
-        console.log(JSON.stringify(message));
-        if ('lid_switch_open' in states[index]) {
-          deviceStateLidSwitchOpen = states[index].lid_switch_open;
-        }
-        if ('hinge_angle_value' in states[index]) {
-          deviceStateHingeAngleValue = states[index].hinge_angle_value;
-          // TODO(b/181157794): Use a custom Sensor HAL for hinge_angle injection
-          // instead of this guest binary.
-          adbShell(
-              '/vendor/bin/cuttlefish_sensor_injection hinge_angle ' +
-              states[index].hinge_angle_value);
-        }
-        // Update the Device Details view.
-        updateDeviceStateDetails();
-        // Cycle to the next state.
-        index = (index + 1) % states.length;
-      }
-    }
-  }
-
-  function startMouseTracking() {
-    let deviceDisplayList = document.getElementsByClassName("device-display");
-    if (window.PointerEvent) {
-      for (const deviceDisplay of deviceDisplayList) {
-        deviceDisplay.addEventListener('pointerdown', onStartDrag);
-        deviceDisplay.addEventListener('pointermove', onContinueDrag);
-        deviceDisplay.addEventListener('pointerup', onEndDrag);
-      }
-    } else if (window.TouchEvent) {
-      for (const deviceDisplay of deviceDisplayList) {
-        deviceDisplay.addEventListener('touchstart', onStartDrag);
-        deviceDisplay.addEventListener('touchmove', onContinueDrag);
-        deviceDisplay.addEventListener('touchend', onEndDrag);
-      }
-    } else if (window.MouseEvent) {
-      for (const deviceDisplay of deviceDisplayList) {
-        deviceDisplay.addEventListener('mousedown', onStartDrag);
-        deviceDisplay.addEventListener('mousemove', onContinueDrag);
-        deviceDisplay.addEventListener('mouseup', onEndDrag);
-      }
-    }
-  }
-
-  function stopMouseTracking() {
-    let deviceDisplayList = document.getElementsByClassName("device-display");
-    if (window.PointerEvent) {
-      for (const deviceDisplay of deviceDisplayList) {
-        deviceDisplay.removeEventListener('pointerdown', onStartDrag);
-        deviceDisplay.removeEventListener('pointermove', onContinueDrag);
-        deviceDisplay.removeEventListener('pointerup', onEndDrag);
-      }
-    } else if (window.TouchEvent) {
-      for (const deviceDisplay of deviceDisplayList) {
-        deviceDisplay.removeEventListener('touchstart', onStartDrag);
-        deviceDisplay.removeEventListener('touchmove', onContinueDrag);
-        deviceDisplay.removeEventListener('touchend', onEndDrag);
-      }
-    } else if (window.MouseEvent) {
-      for (const deviceDisplay of deviceDisplayList) {
-        deviceDisplay.removeEventListener('mousedown', onStartDrag);
-        deviceDisplay.removeEventListener('mousemove', onContinueDrag);
-        deviceDisplay.removeEventListener('mouseup', onEndDrag);
-      }
-    }
-  }
-
-  function startKeyboardTracking() {
-    document.addEventListener('keydown', onKeyEvent);
-    document.addEventListener('keyup', onKeyEvent);
-  }
-
-  function stopKeyboardTracking() {
-    document.removeEventListener('keydown', onKeyEvent);
-    document.removeEventListener('keyup', onKeyEvent);
-  }
-
-  function onStartDrag(e) {
-    e.preventDefault();
-
-    // console.log("mousedown at " + e.pageX + " / " + e.pageY);
-    mouseIsDown = true;
-
-    sendEventUpdate(true, e);
-  }
-
-  function onEndDrag(e) {
-    e.preventDefault();
-
-    // console.log("mouseup at " + e.pageX + " / " + e.pageY);
-    mouseIsDown = false;
-
-    sendEventUpdate(false, e);
-  }
-
-  function onContinueDrag(e) {
-    e.preventDefault();
-
-    // console.log("mousemove at " + e.pageX + " / " + e.pageY + ", down=" +
-    // mouseIsDown);
-    if (mouseIsDown) {
-      sendEventUpdate(true, e);
-    }
-  }
-
-  function sendEventUpdate(down, e) {
-    console.assert(deviceConnection, 'Can\'t send mouse update without device');
-    var eventType = e.type.substring(0, 5);
-
-    // The <video> element:
-    const deviceDisplay = e.target;
-
-    // Before the first video frame arrives there is no way to know width and
-    // height of the device's screen, so turn every click into a click at 0x0.
-    // A click at that position is not more dangerous than anywhere else since
-    // the user is clicking blind anyways.
-    const videoWidth = deviceDisplay.videoWidth? deviceDisplay.videoWidth: 1;
-    const videoHeight = deviceDisplay.videoHeight? deviceDisplay.videoHeight: 1;
-    const elementWidth = deviceDisplay.offsetWidth? deviceDisplay.offsetWidth: 1;
-    const elementHeight = deviceDisplay.offsetHeight? deviceDisplay.offsetHeight: 1;
-
-    // vh*ew > eh*vw? then scale h instead of w
-    const scaleHeight = videoHeight * elementWidth > videoWidth * elementHeight;
-    var elementScaling = 0, videoScaling = 0;
-    if (scaleHeight) {
-      elementScaling = elementHeight;
-      videoScaling = videoHeight;
-    } else {
-      elementScaling = elementWidth;
-      videoScaling = videoWidth;
-    }
-
-    // The screen uses the 'object-fit: cover' property in order to completely
-    // fill the element while maintaining the screen content's aspect ratio.
-    // Therefore:
-    // - If vh*ew > eh*vw, w is scaled so that content width == element width
-    // - Otherwise,        h is scaled so that content height == element height
-    const scaleWidth = videoHeight * elementWidth > videoWidth * elementHeight;
-
-    // Convert to coordinates relative to the video by scaling.
-    // (This matches the scaling used by 'object-fit: cover'.)
-    //
-    // This scaling is needed to translate from the in-browser x/y to the
-    // on-device x/y.
-    //   - When the device screen has not been resized, this is simple: scale
-    //     the coordinates based on the ratio between the input video size and
-    //     the in-browser size.
-    //   - When the device screen has been resized, this scaling is still needed
-    //     even though the in-browser size and device size are identical. This
-    //     is due to the way WindowManager handles a resized screen, resized via
-    //     `adb shell wm size`:
-    //       - The ABS_X and ABS_Y max values of the screen retain their
-    //         original values equal to the value set when launching the device
-    //         (which equals the video size here).
-    //       - The sent ABS_X and ABS_Y values need to be scaled based on the
-    //         ratio between the max size (video size) and in-browser size.
-    const scaling = scaleWidth ? videoWidth / elementWidth : videoHeight / elementHeight;
-
-    var xArr = [];
-    var yArr = [];
-    var idArr = [];
-    var slotArr = [];
-
-    if (eventType == "mouse" || eventType == "point") {
-      xArr.push(e.offsetX);
-      yArr.push(e.offsetY);
-
-      let thisId = -1;
-      if (eventType == "point") {
-        thisId = e.pointerId;
-      }
-
-      slotArr.push(0);
-      idArr.push(thisId);
-    } else if (eventType == "touch") {
-      // touchstart: list of touch points that became active
-      // touchmove: list of touch points that changed
-      // touchend: list of touch points that were removed
-      let changes = e.changedTouches;
-      let rect = e.target.getBoundingClientRect();
-      for (var i=0; i < changes.length; i++) {
-        xArr.push(changes[i].pageX - rect.left);
-        yArr.push(changes[i].pageY - rect.top);
-        if (touchIdSlotMap.has(changes[i].identifier)) {
-          let slot = touchIdSlotMap.get(changes[i].identifier);
-
-          slotArr.push(slot);
-          if (e.type == 'touchstart') {
-            // error
-            console.error('touchstart when already have slot');
-            return;
-          } else if (e.type == 'touchmove') {
-            idArr.push(changes[i].identifier);
-          } else if (e.type == 'touchend') {
-            touchSlots[slot] = false;
-            touchIdSlotMap.delete(changes[i].identifier);
-            idArr.push(-1);
-          }
-        } else {
-          if (e.type == 'touchstart') {
-            let slot = -1;
-            for (var j=0; j < touchSlots.length; j++) {
-              if (!touchSlots[j]) {
-                slot = j;
-                break;
-              }
-            }
-            if (slot == -1) {
-              slot = touchSlots.length;
-              touchSlots.push(true);
-            }
-            slotArr.push(slot);
-            touchSlots[slot] = true;
-            touchIdSlotMap.set(changes[i].identifier, slot);
-            idArr.push(changes[i].identifier);
-          } else if (e.type == 'touchmove') {
-            // error
-            console.error('touchmove when no slot');
-            return;
-          } else if (e.type == 'touchend') {
-            // error
-            console.error('touchend when no slot');
-            return;
-          }
-        }
-      }
-    }
-
-    for (var i=0; i < xArr.length; i++) {
-      xArr[i] = xArr[i] * scaling;
-      yArr[i] = yArr[i] * scaling;
-
-      // Substract the offset produced by the difference in aspect ratio, if any.
-      if (scaleWidth) {
-        // Width was scaled, leaving excess content height, so subtract from y.
-        yArr[i] -= (elementHeight * scaling - videoHeight) / 2;
-      } else {
-        // Height was scaled, leaving excess content width, so subtract from x.
-        xArr[i] -= (elementWidth * scaling - videoWidth) / 2;
-      }
-
-      xArr[i] = Math.trunc(xArr[i]);
-      yArr[i] = Math.trunc(yArr[i]);
-    }
-
-    // NOTE: Rotation is handled automatically because the CSS rotation through
-    // transforms also rotates the coordinates of events on the object.
-
-    const display_label = deviceDisplay.id;
-
-    deviceConnection.sendMultiTouch(
-    {idArr, xArr, yArr, down, slotArr, display_label});
-  }
-
-  function onKeyEvent(e) {
-    e.preventDefault();
-    console.assert(deviceConnection, 'Can\'t send key event without device');
-    deviceConnection.sendKeyEvent(e.code, e.type);
-  }
-}
-
-/******************************************************************************/
-
-function ConnectDeviceCb(dev_id) {
-  console.log('Connect: ' + dev_id);
-  // Hide the device selection screen
-  document.getElementById('device-selector').style.display = 'none';
-  // Show the device control screen
-  document.getElementById('device-connection').style.visibility = 'visible';
-  ConnectToDevice(dev_id);
-}
-
-function ShowNewDeviceList(device_ids) {
-  let ul = document.getElementById('device-list');
-  ul.innerHTML = "";
-  let count = 1;
-  let device_to_button_map = {};
-  for (const dev_id of device_ids) {
-    const button_id = 'connect_' + count++;
-    ul.innerHTML += ('<li class="device_entry" title="Connect to ' + dev_id
-                     + '">' + dev_id + '<button id="' + button_id
-                     + '" >Connect</button></li>');
-    device_to_button_map[dev_id] = button_id;
-  }
-
-  for (const [dev_id, button_id] of Object.entries(device_to_button_map)) {
-    document.getElementById(button_id).addEventListener(
-        'click', evt => ConnectDeviceCb(dev_id));
-  }
-}
-
-function UpdateDeviceList() {
-  let url = ((location.protocol == 'http:') ? 'ws:' : 'wss:') + location.host +
-    '/list_devices';
-  let ws = new WebSocket(url);
-  ws.onopen = () => {
-    ws.send("give me those device ids");
-  };
-  ws.onmessage = msg => {
-   let device_ids = JSON.parse(msg.data);
-    ShowNewDeviceList(device_ids);
-  };
-}
-
-// Get any devices that are already connected
-UpdateDeviceList();
-// Update the list at the user's request
-document.getElementById('refresh-list')
-    .addEventListener('click', evt => UpdateDeviceList());
diff --git a/host/frontend/webrtc_operator/assets/js/cf_webrtc.js b/host/frontend/webrtc_operator/assets/js/cf_webrtc.js
deleted file mode 100644
index c83ee38..0000000
--- a/host/frontend/webrtc_operator/assets/js/cf_webrtc.js
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function createDataChannel(pc, label, onMessage) {
-  console.log('creating data channel: ' + label);
-  let dataChannel = pc.createDataChannel(label);
-  // Return an object with a send function like that of the dataChannel, but
-  // that only actually sends over the data channel once it has connected.
-  return {
-    channelPromise: new Promise((resolve, reject) => {
-      dataChannel.onopen = (event) => {
-        resolve(dataChannel);
-      };
-      dataChannel.onclose = () => {
-        console.log(
-            'Data channel=' + label + ' state=' + dataChannel.readyState);
-      };
-      dataChannel.onmessage = onMessage ? onMessage : (msg) => {
-        console.log('Data channel=' + label + ' data="' + msg.data + '"');
-      };
-      dataChannel.onerror = err => {
-        reject(err);
-      };
-    }),
-    send: function(msg) {
-      this.channelPromise = this.channelPromise.then(channel => {
-        channel.send(msg);
-        return channel;
-      })
-    },
-  };
-}
-
-function awaitDataChannel(pc, label, onMessage) {
-  console.log('expecting data channel: ' + label);
-  // Return an object with a send function like that of the dataChannel, but
-  // that only actually sends over the data channel once it has connected.
-  return {
-    channelPromise: new Promise((resolve, reject) => {
-      let prev_ondatachannel = pc.ondatachannel;
-      pc.ondatachannel = ev => {
-        let dataChannel = ev.channel;
-        if (dataChannel.label == label) {
-          dataChannel.onopen = (event) => {
-            resolve(dataChannel);
-          };
-          dataChannel.onclose = () => {
-            console.log(
-                'Data channel=' + label + ' state=' + dataChannel.readyState);
-          };
-          dataChannel.onmessage = onMessage ? onMessage : (msg) => {
-            console.log('Data channel=' + label + ' data="' + msg.data + '"');
-          };
-          dataChannel.onerror = err => {
-            reject(err);
-          };
-        } else if (prev_ondatachannel) {
-          prev_ondatachannel(ev);
-        }
-      };
-    }),
-    send: function(msg) {
-      this.channelPromise = this.channelPromise.then(channel => {
-        channel.send(msg);
-        return channel;
-      })
-    },
-  };
-}
-
-class DeviceConnection {
-  constructor(pc, control, media_stream) {
-    this._pc = pc;
-    this._control = control;
-    this._media_stream = media_stream;
-    // Disable the microphone by default
-    this.useMic(false);
-    this.useVideo(false);
-    this._cameraDataChannel = pc.createDataChannel('camera-data-channel');
-    this._cameraDataChannel.binaryType = 'arraybuffer'
-    this._cameraInputQueue = new Array();
-    var self = this;
-    this._cameraDataChannel.onbufferedamountlow = () => {
-      if (self._cameraInputQueue.length > 0) {
-        self.sendCameraData(self._cameraInputQueue.shift());
-      }
-    }
-    this._inputChannel = createDataChannel(pc, 'input-channel');
-    this._adbChannel = createDataChannel(pc, 'adb-channel', (msg) => {
-      if (this._onAdbMessage) {
-        this._onAdbMessage(msg.data);
-      } else {
-        console.error('Received unexpected ADB message');
-      }
-    });
-    this._controlChannel = awaitDataChannel(pc, 'device-control', (msg) => {
-      if (this._onControlMessage) {
-        this._onControlMessage(msg);
-      } else {
-        console.error('Received unexpected Control message');
-      }
-    });
-    this._bluetoothChannel = createDataChannel(pc, 'bluetooth-channel', (msg) => {
-      if (this._onBluetoothMessage) {
-        this._onBluetoothMessage(msg.data);
-      } else {
-        console.error('Received unexpected Bluetooth message');
-      }
-    });
-    this.sendCameraResolution();
-    this._streams = {};
-    this._streamPromiseResolvers = {};
-
-    pc.addEventListener('track', e => {
-      console.log('Got remote stream: ', e);
-      for (const stream of e.streams) {
-        this._streams[stream.id] = stream;
-        if (this._streamPromiseResolvers[stream.id]) {
-          for (let resolver of this._streamPromiseResolvers[stream.id]) {
-            resolver();
-          }
-          delete this._streamPromiseResolvers[stream.id];
-        }
-      }
-    });
-  }
-
-  set description(desc) {
-    this._description = desc;
-  }
-
-  get description() {
-    return this._description;
-  }
-
-  get imageCapture() {
-    if (this._media_stream) {
-      const track = this._media_stream.getVideoTracks()[0]
-      return new ImageCapture(track);
-    }
-    return undefined;
-  }
-
-  get cameraWidth() {
-    return this._x_res;
-  }
-
-  get cameraHeight() {
-    return this._y_res;
-  }
-
-  get cameraEnabled() {
-    if (this._media_stream) {
-      return this._media_stream.getVideoTracks().some(track => track.enabled);
-    }
-  }
-
-  getStream(stream_id) {
-    return new Promise((resolve, reject) => {
-      if (this._streams[stream_id]) {
-        resolve(this._streams[stream_id]);
-      } else {
-        if (!this._streamPromiseResolvers[stream_id]) {
-          this._streamPromiseResolvers[stream_id] = [];
-        }
-        this._streamPromiseResolvers[stream_id].push(resolve);
-      }
-    });
-  }
-
-  _sendJsonInput(evt) {
-    this._inputChannel.send(JSON.stringify(evt));
-  }
-
-  sendMousePosition({x, y, down, display_label}) {
-    this._sendJsonInput({
-      type: 'mouse',
-      down: down ? 1 : 0,
-      x,
-      y,
-      display_label,
-    });
-  }
-
-  // TODO (b/124121375): This should probably be an array of pointer events and
-  // have different properties.
-  sendMultiTouch({idArr, xArr, yArr, down, slotArr, display_label}) {
-    this._sendJsonInput({
-      type: 'multi-touch',
-      id: idArr,
-      x: xArr,
-      y: yArr,
-      down: down ? 1 : 0,
-      slot: slotArr,
-      display_label: display_label,
-    });
-  }
-
-  sendKeyEvent(code, type) {
-    this._sendJsonInput({type: 'keyboard', keycode: code, event_type: type});
-  }
-
-  disconnect() {
-    this._pc.close();
-  }
-
-  // Sends binary data directly to the in-device adb daemon (skipping the host)
-  sendAdbMessage(msg) {
-    this._adbChannel.send(msg);
-  }
-
-  // Provide a callback to receive data from the in-device adb daemon
-  onAdbMessage(cb) {
-    this._onAdbMessage = cb;
-  }
-
-  // Send control commands to the device
-  sendControlMessage(msg) {
-    this._controlChannel.send(msg);
-  }
-
-  useMic(in_use) {
-    if (this._media_stream) {
-      this._media_stream.getAudioTracks().forEach(track => track.enabled = in_use);
-    }
-  }
-
-  useVideo(in_use) {
-    if (this._media_stream) {
-      this._media_stream.getVideoTracks().forEach(track => track.enabled = in_use);
-    }
-  }
-
-  sendCameraResolution() {
-    if (this._media_stream) {
-      const cameraTracks = this._media_stream.getVideoTracks();
-      if (cameraTracks.length > 0) {
-        const settings = cameraTracks[0].getSettings();
-        this._x_res = settings.width;
-        this._y_res = settings.height;
-        this.sendControlMessage(JSON.stringify({
-          command: 'camera_settings',
-          width: settings.width,
-          height: settings.height,
-          frame_rate: settings.frameRate,
-          facing: settings.facingMode
-        }));
-      }
-    }
-  }
-
-  sendOrQueueCameraData(data) {
-    if (this._cameraDataChannel.bufferedAmount > 0 || this._cameraInputQueue.length > 0) {
-      this._cameraInputQueue.push(data);
-    } else {
-      this.sendCameraData(data);
-    }
-  }
-
-  sendCameraData(data) {
-    const MAX_SIZE = 65535;
-    const END_MARKER = 'EOF';
-    for (let i = 0; i < data.byteLength; i += MAX_SIZE) {
-      // range is clamped to the valid index range
-      this._cameraDataChannel.send(data.slice(i, i + MAX_SIZE));
-    }
-    this._cameraDataChannel.send(END_MARKER);
-  }
-
-  // Provide a callback to receive control-related comms from the device
-  onControlMessage(cb) {
-    this._onControlMessage = cb;
-  }
-
-  sendBluetoothMessage(msg) {
-    this._bluetoothChannel.send(msg);
-  }
-
-  onBluetoothMessage(cb) {
-    this._onBluetoothMessage = cb;
-  }
-
-  // Provide a callback to receive connectionstatechange states.
-  onConnectionStateChange(cb) {
-    this._pc.addEventListener(
-      'connectionstatechange',
-      evt => cb(this._pc.connectionState));
-  }
-}
-
-
-class WebRTCControl {
-  constructor({
-    wsUrl = '',
-  }) {
-    /*
-     * Private attributes:
-     *
-     * _wsPromise: promises the underlying websocket, should resolve when the
-     *             socket passes to OPEN state, will be rejecte/replaced by a
-     *             rejected promise if an error is detected on the socket.
-     *
-     * _onOffer
-     * _onIceCandidate
-     */
-
-    this._promiseResolvers = {};
-
-    this._wsPromise = new Promise((resolve, reject) => {
-      let ws = new WebSocket(wsUrl);
-      ws.onopen = () => {
-        console.info(`Connected to ${wsUrl}`);
-        resolve(ws);
-      };
-      ws.onerror = evt => {
-        console.error('WebSocket error:', evt);
-        reject(evt);
-        // If the promise was already resolved the previous line has no effect
-        this._wsPromise = Promise.reject(new Error(evt));
-      };
-      ws.onmessage = e => {
-        let data = JSON.parse(e.data);
-        this._onWebsocketMessage(data);
-      };
-    });
-  }
-
-  _onWebsocketMessage(message) {
-    const type = message.message_type;
-    if (message.error) {
-      console.error(message.error);
-      this._on_connection_failed(message.error);
-      return;
-    }
-    switch (type) {
-      case 'config':
-        this._infra_config = message;
-        break;
-      case 'device_info':
-        if (this._on_device_available) {
-          this._on_device_available(message.device_info);
-          delete this._on_device_available;
-        } else {
-          console.error('Received unsolicited device info');
-        }
-        break;
-      case 'device_msg':
-        this._onDeviceMessage(message.payload);
-        break;
-      default:
-        console.error('Unrecognized message type from server: ', type);
-        this._on_connection_failed('Unrecognized message type from server: ' + type);
-        console.error(message);
-    }
-  }
-
-  _onDeviceMessage(message) {
-    let type = message.type;
-    switch (type) {
-      case 'offer':
-        if (this._onOffer) {
-          this._onOffer({type: 'offer', sdp: message.sdp});
-        } else {
-          console.error('Receive offer, but nothing is wating for it');
-        }
-        break;
-      case 'ice-candidate':
-        if (this._onIceCandidate) {
-          this._onIceCandidate(new RTCIceCandidate({
-            sdpMid: message.mid,
-            sdpMLineIndex: message.mLineIndex,
-            candidate: message.candidate
-          }));
-        } else {
-          console.error('Received ice candidate but nothing is waiting for it');
-        }
-        break;
-      default:
-        console.error('Unrecognized message type from device: ', type);
-    }
-  }
-
-  async _wsSendJson(obj) {
-    let ws = await this._wsPromise;
-    return ws.send(JSON.stringify(obj));
-  }
-  async _sendToDevice(payload) {
-    this._wsSendJson({message_type: 'forward', payload});
-  }
-
-  onOffer(cb) {
-    this._onOffer = cb;
-  }
-
-  onIceCandidate(cb) {
-    this._onIceCandidate = cb;
-  }
-
-  async requestDevice(device_id) {
-    return new Promise((resolve, reject) => {
-      this._on_device_available = (deviceInfo) => resolve({
-        deviceInfo,
-        infraConfig: this._infra_config,
-      });
-      this._on_connection_failed = (error) => reject(error);
-      this._wsSendJson({
-        message_type: 'connect',
-        device_id,
-      });
-    });
-  }
-
-  ConnectDevice() {
-    console.log('ConnectDevice');
-    this._sendToDevice({type: 'request-offer'});
-  }
-
-  /**
-   * Sends a remote description to the device.
-   */
-  async sendClientDescription(desc) {
-    console.log('sendClientDescription');
-    this._sendToDevice({type: 'answer', sdp: desc.sdp});
-  }
-
-  /**
-   * Sends an ICE candidate to the device
-   */
-  async sendIceCandidate(candidate) {
-    this._sendToDevice({type: 'ice-candidate', candidate});
-  }
-}
-
-function createPeerConnection(infra_config) {
-  let pc_config = {iceServers: []};
-  for (const stun of infra_config.ice_servers) {
-    pc_config.iceServers.push({urls: 'stun:' + stun});
-  }
-  let pc = new RTCPeerConnection(pc_config);
-
-  pc.addEventListener('icecandidate', evt => {
-    console.log('Local ICE Candidate: ', evt.candidate);
-  });
-  pc.addEventListener('iceconnectionstatechange', evt => {
-    console.log(`ICE State Change: ${pc.iceConnectionState}`);
-  });
-  pc.addEventListener(
-      'connectionstatechange',
-      evt =>
-          console.log(`WebRTC Connection State Change: ${pc.connectionState}`));
-  return pc;
-}
-
-export async function Connect(deviceId, options) {
-  let control = new WebRTCControl(options);
-  let requestRet = await control.requestDevice(deviceId);
-  let deviceInfo = requestRet.deviceInfo;
-  let infraConfig = requestRet.infraConfig;
-  console.log('Device available:');
-  console.log(deviceInfo);
-  let pc_config = {iceServers: []};
-  if (infraConfig.ice_servers && infraConfig.ice_servers.length > 0) {
-    for (const server of infraConfig.ice_servers) {
-      pc_config.iceServers.push(server);
-    }
-  }
-  let pc = createPeerConnection(infraConfig, control);
-
-  let mediaStream;
-  try {
-    mediaStream =
-        await navigator.mediaDevices.getUserMedia({video: true, audio: true});
-    const tracks = mediaStream.getTracks();
-    tracks.forEach(track => {
-      console.log(`Using ${track.kind} device: ${track.label}`);
-      pc.addTrack(track, mediaStream);
-    });
-  } catch (e) {
-    console.error("Failed to open audio device: ", e);
-  }
-
-  let deviceConnection = new DeviceConnection(pc, control, mediaStream);
-  deviceConnection.description = deviceInfo;
-  async function acceptOfferAndReplyAnswer(offer) {
-    try {
-      await pc.setRemoteDescription(offer);
-      let answer = await pc.createAnswer();
-      console.log('Answer: ', answer);
-      await pc.setLocalDescription(answer);
-      await control.sendClientDescription(answer);
-    } catch (e) {
-      console.error('Error establishing WebRTC connection: ', e)
-      throw e;
-    }
-  }
-  control.onOffer(desc => {
-    console.log('Offer: ', desc);
-    acceptOfferAndReplyAnswer(desc);
-  });
-  control.onIceCandidate(iceCandidate => {
-    console.log(`Remote ICE Candidate: `, iceCandidate);
-    pc.addIceCandidate(iceCandidate);
-  });
-
-  pc.addEventListener('icecandidate', evt => {
-    if (evt.candidate) control.sendIceCandidate(evt.candidate);
-  });
-  let connected_promise = new Promise((resolve, reject) => {
-    pc.addEventListener('connectionstatechange', evt => {
-      let state = pc.connectionState;
-      if (state == 'connected') {
-        resolve(deviceConnection);
-      } else if (state == 'failed') {
-        reject(evt);
-      }
-    });
-  });
-  control.ConnectDevice();
-
-  return connected_promise;
-}
diff --git a/host/frontend/webrtc_operator/assets/js/controls.js b/host/frontend/webrtc_operator/assets/js/controls.js
deleted file mode 100644
index 31db046..0000000
--- a/host/frontend/webrtc_operator/assets/js/controls.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function createToggleControl(elm, iconName, onChangeCb) {
-  let icon = document.createElement("span");
-  icon.classList.add("toggle-control-icon");
-  icon.classList.add("material-icons-outlined");
-  if (iconName) {
-    icon.appendChild(document.createTextNode(iconName));
-  }
-  elm.appendChild(icon);
-  let toggle = document.createElement("label");
-  toggle.classList.add("toggle-control-switch");
-  let input = document.createElement("input");
-  input.type = "checkbox";
-  toggle.appendChild(input);
-  let slider = document.createElement("span");
-  slider.classList.add("toggle-control-slider");
-  toggle.appendChild(slider);
-  elm.classList.add("toggle-control");
-  elm.appendChild(toggle);
-  if (onChangeCb) {
-    input.onchange = e => onChangeCb(e.target.checked);
-  }
-}
diff --git a/host/frontend/webrtc_operator/assets/js/index.js b/host/frontend/webrtc_operator/assets/js/index.js
new file mode 100644
index 0000000..c04f308
--- /dev/null
+++ b/host/frontend/webrtc_operator/assets/js/index.js
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+class DeviceListApp {
+  #url;
+  #selectDeviceCb;
+
+  constructor({url, selectDeviceCb}) {
+    this.#url = url;
+    this.#selectDeviceCb = selectDeviceCb;
+  }
+
+  start() {
+    // Get any devices that are already connected
+    this.#UpdateDeviceList();
+
+    // Update the list at the user's request
+    document.getElementById('refresh-list')
+        .addEventListener('click', evt => this.#UpdateDeviceList());
+  }
+
+  async #UpdateDeviceList() {
+    try {
+      const device_ids = await fetch(this.#url, {
+        method: 'GET',
+        cache: 'no-cache',
+        redirect: 'follow',
+      });
+      this.#ShowNewDeviceList(await device_ids.json());
+    } catch (e) {
+      console.error('Error getting list of device ids: ', e);
+    }
+  }
+
+  #ShowNewDeviceList(device_ids) {
+    let ul = document.getElementById('device-list');
+    ul.innerHTML = '';
+    let count = 1;
+    let device_to_button_map = {};
+    for (const devId of device_ids) {
+      const buttonId = 'connect_' + count++;
+      let entry = this.#createDeviceEntry(devId, buttonId);
+      ul.appendChild(entry);
+      device_to_button_map[devId] = buttonId;
+    }
+
+    for (const [devId, buttonId] of Object.entries(device_to_button_map)) {
+      let button = document.getElementById(buttonId);
+      button.addEventListener('click', evt => {
+        this.#selectDeviceCb(devId);
+      });
+    }
+  }
+
+  #createDeviceEntry(devId, buttonId) {
+    let li = document.createElement('li');
+    li.className = 'device_entry';
+    li.title = 'Connect to ' + devId;
+    let div = document.createElement('div');
+    let span = document.createElement('span');
+    span.appendChild(document.createTextNode(devId));
+    let button = document.createElement('button');
+    button.id = buttonId;
+    button.appendChild(document.createTextNode('Connect'));
+    div.appendChild(span);
+    div.appendChild(button);
+    li.appendChild(div);
+    return li;
+  }
+}  // DeviceListApp
+
+window.addEventListener('load', e => {
+  let listDevicesUrl = '/devices';
+  let selectDeviceCb = deviceId => {
+    return new Promise((resolve, reject) => {
+      let client = window.open(`client.html?deviceId=${deviceId}`, deviceId);
+      client.addEventListener('load', evt => {
+        console.log('loaded');
+        resolve();
+      });
+    });
+  };
+  let deviceListApp = new DeviceListApp({url: listDevicesUrl, selectDeviceCb});
+  deviceListApp.start();
+});
diff --git a/host/frontend/webrtc_operator/assets/js/server_connector.js b/host/frontend/webrtc_operator/assets/js/server_connector.js
new file mode 100644
index 0000000..ff19a0a
--- /dev/null
+++ b/host/frontend/webrtc_operator/assets/js/server_connector.js
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+// The public elements in this file implement the Server Connector Interface,
+// part of the contract between the signaling server and the webrtc client.
+// No changes that break backward compatibility are allowed here. Any new
+// features must be added as a new function/class in the interface. Any
+// additions to the interface must be checked for existence by the client before
+// using it.
+
+// The id of the device the client is supposed to connect to.
+// The List Devices page in the signaling server may choose any way to pass the
+// device id to the client page, this function retrieves that information once
+// the client loaded.
+// In this case the device id is passed as a parameter in the url.
+export function deviceId() {
+  const urlParams = new URLSearchParams(window.location.search);
+  return urlParams.get('deviceId');
+}
+
+// Creates a connector capable of communicating with the signaling server.
+export async function createConnector() {
+  try {
+    let ws = await connectWs();
+    console.debug(`Connected to ${ws.url}`);
+    return new WebsocketConnector(ws);
+  } catch (e) {
+    console.error('WebSocket error:', e);
+  }
+  console.warn('Failed to connect websocket, trying polling instead');
+
+  return new PollingConnector();
+}
+
+// A connector object provides high level functions for communicating with the
+// signaling server, while hiding away implementation details.
+// This class is an interface and shouldn't be instantiated direclty.
+// Only the public methods present in this class form part of the Server
+// Connector Interface, any implementations of the interface are considered
+// internal and not accessible to client code.
+class Connector {
+  constructor() {
+    if (this.constructor == Connector) {
+      throw new Error('Connector is an abstract class');
+    }
+  }
+
+  // Selects a particular device in the signaling server and opens the signaling
+  // channel with it (but doesn't send any message to the device). Returns a
+  // promise to an object with the following properties:
+  // - deviceInfo: The info object provided by the device when it registered
+  // with the server.
+  // - infraConfig: The server's infrastructure configuration (mainly STUN and
+  // TURN servers)
+  // The promise may take a long time to resolve if, for example, the server
+  // decides to wait for a device with the provided id to register with it. The
+  // promise may be rejected if there are connectivity issues, a device with
+  // that id doesn't exist or this client doesn't have rights to access that
+  // device.
+  async requestDevice(deviceId) {
+    throw 'Not implemented!';
+  }
+
+  // Sends a message to the device selected with requestDevice. It's an error to
+  // call this function before the promise from requestDevice() has resolved.
+  // Returns an empty promise that is rejected when the message can not be
+  // delivered, either because the device has not been requested yet or because
+  // of connectivity issues.
+  async sendToDevice(msg) {
+    throw 'Not implemented!';
+  }
+}
+
+// End of Server Connector Interface.
+
+// The following code is internal and shouldn't be accessed outside this file.
+
+function httpUrl(path) {
+  return location.protocol + '//' + location.host + '/' + path;
+}
+
+function websocketUrl(path) {
+  return ((location.protocol == 'http:') ? 'ws://' : 'wss://') + location.host +
+      '/' + path;
+}
+
+const kPollConfigUrl = httpUrl('infra_config');
+const kPollConnectUrl = httpUrl('connect');
+const kPollForwardUrl = httpUrl('forward');
+const kPollMessagesUrl = httpUrl('poll_messages');
+
+async function connectWs() {
+  return new Promise((resolve, reject) => {
+    let url = websocketUrl('connect_client');
+    let ws = new WebSocket(url);
+    ws.onopen = () => {
+      resolve(ws);
+    };
+    ws.onerror = evt => {
+      reject(evt);
+    };
+  });
+}
+
+async function ajaxPostJson(url, data) {
+  const response = await fetch(url, {
+    method: 'POST',
+    cache: 'no-cache',
+    headers: {'Content-Type': 'application/json'},
+    redirect: 'follow',
+    body: JSON.stringify(data),
+  });
+  return response.json();
+}
+
+// Implementation of the connector interface using websockets
+class WebsocketConnector extends Connector {
+  #websocket;
+  #futures = {};
+  #onDeviceMsgCb = msg =>
+      console.error('Received device message without registered listener');
+
+  onDeviceMsg(cb) {
+    this.#onDeviceMsgCb = cb;
+  }
+
+  constructor(ws) {
+    super();
+    ws.onmessage = e => {
+      let data = JSON.parse(e.data);
+      this.#onWebsocketMessage(data);
+    };
+    this.#websocket = ws;
+  }
+
+  async requestDevice(deviceId) {
+    return new Promise((resolve, reject) => {
+      this.#futures.onDeviceAvailable = (device) => resolve(device);
+      this.#futures.onConnectionFailed = (error) => reject(error);
+      this.#wsSendJson({
+        message_type: 'connect',
+        device_id: deviceId,
+      });
+    });
+  }
+
+  async sendToDevice(msg) {
+    return this.#wsSendJson({message_type: 'forward', payload: msg});
+  }
+
+  #onWebsocketMessage(message) {
+    const type = message.message_type;
+    if (message.error) {
+      console.error(message.error);
+      this.#futures.onConnectionFailed(message.error);
+      return;
+    }
+    switch (type) {
+      case 'config':
+        this.#futures.infraConfig = message;
+        break;
+      case 'device_info':
+        if (this.#futures.onDeviceAvailable) {
+          this.#futures.onDeviceAvailable({
+            deviceInfo: message.device_info,
+            infraConfig: this.#futures.infraConfig,
+          });
+          delete this.#futures.onDeviceAvailable;
+        } else {
+          console.error('Received unsolicited device info');
+        }
+        break;
+      case 'device_msg':
+        this.#onDeviceMsgCb(message.payload);
+        break;
+      default:
+        console.error('Unrecognized message type from server: ', type);
+        this.#futures.onConnectionFailed(
+            'Unrecognized message type from server: ' + type);
+        console.error(message);
+    }
+  }
+
+  async #wsSendJson(obj) {
+    return this.#websocket.send(JSON.stringify(obj));
+  }
+}
+
+// Implementation of the Connector interface using HTTP long polling
+class PollingConnector extends Connector {
+  #connId = undefined;
+  #config = undefined;
+  #pollerSchedule;
+  #onDeviceMsgCb = msg =>
+      console.error('Received device message without registered listener');
+
+  onDeviceMsg(cb) {
+    this.#onDeviceMsgCb = cb;
+  }
+
+  constructor() {
+    super();
+  }
+
+  async requestDevice(deviceId) {
+    let config = await this.#getConfig();
+    let response = await ajaxPostJson(kPollConnectUrl, {device_id: deviceId});
+    this.#connId = response.connection_id;
+
+    this.#startPolling();
+
+    return {
+      deviceInfo: response.device_info,
+      infraConfig: config,
+    };
+  }
+
+  async sendToDevice(msg) {
+    // Forward messages act like polling messages as well
+    let device_messages = await this.#forward(msg);
+    for (const message of device_messages) {
+      this.#onDeviceMsgCb(message);
+    }
+  }
+
+  async #getConfig() {
+    if (this.#config === undefined) {
+      this.#config = await (await fetch(kPollConfigUrl, {
+                       method: 'GET',
+                       redirect: 'follow',
+                     })).json();
+    }
+    return this.#config;
+  }
+
+  async #forward(msg) {
+    return await ajaxPostJson(kPollForwardUrl, {
+      connection_id: this.#connId,
+      payload: msg,
+    });
+  }
+
+  async #pollMessages() {
+    return await ajaxPostJson(kPollMessagesUrl, {
+      connection_id: this.#connId,
+    });
+  }
+
+  #startPolling() {
+    if (this.#pollerSchedule !== undefined) {
+      return;
+    }
+
+    let currentPollDelay = 1000;
+    let pollerRoutine = async () => {
+      let messages = await this.#pollMessages();
+
+      // Do exponential backoff on the polling up to 60 seconds
+      currentPollDelay = Math.min(60000, 2 * currentPollDelay);
+      for (const message of messages) {
+        this.#onDeviceMsgCb(message);
+        // There is at least one message, poll sooner
+        currentPollDelay = 1000;
+      }
+      this.#pollerSchedule = setTimeout(pollerRoutine, currentPollDelay);
+    };
+
+    this.#pollerSchedule = setTimeout(pollerRoutine, currentPollDelay);
+  }
+}
diff --git a/host/frontend/webrtc_operator/client_handler.cpp b/host/frontend/webrtc_operator/client_handler.cpp
index 7b631a6..a69241c 100644
--- a/host/frontend/webrtc_operator/client_handler.cpp
+++ b/host/frontend/webrtc_operator/client_handler.cpp
@@ -15,6 +15,9 @@
 
 #include "host/frontend/webrtc_operator/client_handler.h"
 
+#include <algorithm>
+#include <random>
+
 #include <android-base/logging.h>
 
 #include "host/frontend/webrtc_operator/constants/signaling_constants.h"
@@ -22,27 +25,42 @@
 
 namespace cuttlefish {
 
-ClientHandler::ClientHandler(struct lws* wsi, DeviceRegistry* registry,
+namespace {
+std::string RandomClientSecret(size_t len) {
+  static constexpr auto chars =
+      "0123456789"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+      "abcdefghijklmnopqrstuvwxyz";
+  std::string ret(len, '\0');
+  std::default_random_engine e{std::random_device{}()};
+  std::uniform_int_distribution<int> random{
+      0, static_cast<int>(std::strlen(chars)) - 1};
+  std::generate_n(ret.begin(), len, [&]() { return chars[random(e)]; });
+  return ret;
+}
+}
+
+ClientWSHandler::ClientWSHandler(struct lws* wsi, DeviceRegistry* registry,
                              const ServerConfig& server_config)
     : SignalHandler(wsi, registry, server_config),
       device_handler_(),
       client_id_(0) {}
 
-void ClientHandler::OnClosed() {
+void ClientWSHandler::OnClosed() {
   auto device_handler = device_handler_.lock();
   if (device_handler) {
     device_handler->SendClientDisconnectMessage(client_id_);
   }
 }
 
-void ClientHandler::SendDeviceMessage(const Json::Value& device_message) {
+void ClientWSHandler::SendDeviceMessage(const Json::Value& device_message) {
   Json::Value message;
   message[webrtc_signaling::kTypeField] = webrtc_signaling::kDeviceMessageType;
   message[webrtc_signaling::kPayloadField] = device_message;
   Reply(message);
 }
 
-void ClientHandler::handleMessage(const std::string& type,
+void ClientWSHandler::handleMessage(const std::string& type,
                                   const Json::Value& message) {
   if (type == webrtc_signaling::kConnectType) {
     handleConnectionRequest(message);
@@ -53,7 +71,7 @@
   }
 }
 
-void ClientHandler::handleConnectionRequest(const Json::Value& message) {
+void ClientWSHandler::handleConnectionRequest(const Json::Value& message) {
   if (client_id_ > 0) {
     LogAndReplyError(
         "Attempt to connect to multiple devices over same websocket");
@@ -90,7 +108,7 @@
   Reply(device_info_reply);
 }
 
-void ClientHandler::handleForward(const Json::Value& message) {
+void ClientWSHandler::handleForward(const Json::Value& message) {
   if (client_id_ == 0) {
     LogAndReplyError("Forward failed: No device asociated to client");
     Close();
@@ -112,14 +130,232 @@
                                     message[webrtc_signaling::kPayloadField]);
 }
 
-ClientHandlerFactory::ClientHandlerFactory(DeviceRegistry* registry,
+ClientWSHandlerFactory::ClientWSHandlerFactory(DeviceRegistry* registry,
                                            const ServerConfig& server_config)
   : registry_(registry),
     server_config_(server_config) {}
 
-std::shared_ptr<WebSocketHandler> ClientHandlerFactory::Build(struct lws* wsi) {
+std::shared_ptr<WebSocketHandler> ClientWSHandlerFactory::Build(struct lws* wsi) {
   return std::shared_ptr<WebSocketHandler>(
-      new ClientHandler(wsi, registry_, server_config_));
+      new ClientWSHandler(wsi, registry_, server_config_));
+}
+
+/******************************************************************************/
+
+class PollConnectionHandler : public ClientHandler {
+ public:
+  PollConnectionHandler() = default;
+
+  void SendDeviceMessage(const Json::Value& message) override {
+    constexpr size_t kMaxMessagesInQueue = 1000;
+    if (messages_.size() > kMaxMessagesInQueue) {
+      LOG(ERROR) << "Polling client " << client_id_ << " reached "
+                 << kMaxMessagesInQueue
+                 << " messages queued. Started to drop messages.";
+      return;
+    }
+    messages_.push_back(message);
+  }
+
+  std::vector<Json::Value> PollMessages() {
+    std::vector<Json::Value> ret;
+    std::swap(ret, messages_);
+    return ret;
+  }
+
+  void SetDeviceHandler(std::weak_ptr<DeviceHandler> device_handler) {
+    device_handler_ = device_handler;
+  }
+
+  void SetClientId(size_t client_id) { client_id_ = client_id; }
+
+  size_t client_id() const { return client_id_; }
+  std::shared_ptr<DeviceHandler> device_handler() const {
+    return device_handler_.lock();
+  }
+
+ private:
+  size_t client_id_ = 0;
+  std::weak_ptr<DeviceHandler> device_handler_;
+  std::vector<Json::Value> messages_;
+};
+
+std::shared_ptr<PollConnectionHandler> PollConnectionStore::Get(
+    const std::string& conn_id) const {
+  if (!handlers_.count(conn_id)) {
+    return nullptr;
+  }
+  return handlers_.at(conn_id);
+}
+
+std::string PollConnectionStore::Add(std::shared_ptr<PollConnectionHandler> handler) {
+  std::string conn_id;
+  do {
+    conn_id = RandomClientSecret(64);
+  } while (handlers_.count(conn_id));
+  handlers_[conn_id] = handler;
+  return conn_id;
+}
+
+ClientDynHandler::ClientDynHandler(struct lws* wsi,
+                                   PollConnectionStore* poll_store)
+    : DynHandler(wsi), poll_store_(poll_store) {}
+
+HttpStatusCode ClientDynHandler::DoGet() {
+  // No message from the client uses the GET method because all of them
+  // change the server state somehow
+  return HttpStatusCode::MethodNotAllowed;
+}
+
+void ClientDynHandler::Reply(const Json::Value& json) {
+  Json::StreamWriterBuilder factory;
+  auto replyAsString = Json::writeString(factory, json);
+  AppendDataOut(replyAsString);
+}
+
+void ClientDynHandler::ReplyError(const std::string& message) {
+  LOG(ERROR) << message;
+  Json::Value reply;
+  reply["type"] = "error";
+  reply["error"] = message;
+  Reply(reply);
+}
+
+HttpStatusCode ClientDynHandler::DoPost() {
+  auto& data = GetDataIn();
+  Json::Value json_message;
+  std::shared_ptr<PollConnectionHandler> poll_handler;
+  if (data.size() > 0) {
+    Json::CharReaderBuilder builder;
+    std::unique_ptr<Json::CharReader> json_reader(builder.newCharReader());
+    std::string error_message;
+    if (!json_reader->parse(data.c_str(), data.c_str() + data.size(), &json_message,
+                            &error_message)) {
+      ReplyError("Error parsing JSON: " + error_message);
+      // Rate limiting would be a good idea here
+      return HttpStatusCode::BadRequest;
+    }
+
+    std::string conn_id;
+    if (json_message.isMember(webrtc_signaling::kClientSecretField)) {
+      conn_id =
+          json_message[webrtc_signaling::kClientSecretField].asString();
+      poll_handler = poll_store_->Get(conn_id);
+      if (!poll_handler) {
+        ReplyError("Error: Unknown connection id" + conn_id);
+        return HttpStatusCode::Unauthorized;
+      }
+    }
+  }
+  return DoPostInner(poll_handler, json_message);
+}
+
+HttpStatusCode ClientDynHandler::Poll(
+    std::shared_ptr<PollConnectionHandler> poll_handler) {
+  if (!poll_handler) {
+    ReplyError("Poll failed: No device associated to client");
+    return HttpStatusCode::Unauthorized;
+  }
+  auto messages = poll_handler->PollMessages();
+  Json::Value reply(Json::arrayValue);
+  for (auto& msg : messages) {
+    reply.append(msg);
+  }
+  Reply(reply);
+  return HttpStatusCode::Ok;
+}
+
+ConnectHandler::ConnectHandler(struct lws* wsi, DeviceRegistry* registry,
+                               PollConnectionStore* poll_store)
+    : ClientDynHandler(wsi, poll_store), registry_(registry) {}
+
+HttpStatusCode ConnectHandler::DoPostInner(
+    std::shared_ptr<PollConnectionHandler> poll_handler,
+    const Json::Value& message) {
+  if (!message.isMember(webrtc_signaling::kDeviceIdField) ||
+      !message[webrtc_signaling::kDeviceIdField].isString()) {
+    ReplyError("Invalid connection request: Missing device id");
+    return HttpStatusCode::BadRequest;
+  }
+  auto device_id = message[webrtc_signaling::kDeviceIdField].asString();
+
+  auto device_handler = registry_->GetDevice(device_id);
+  if (!device_handler) {
+    ReplyError("Connection failed: Device not found: '" + device_id + "'");
+    return HttpStatusCode::NotFound;
+  }
+
+  poll_handler = std::make_shared<PollConnectionHandler>();
+  poll_handler->SetClientId(device_handler->RegisterClient(poll_handler));
+  poll_handler->SetDeviceHandler(device_handler);
+  auto conn_id = poll_store_->Add(poll_handler);
+
+  Json::Value device_info_reply;
+  device_info_reply[webrtc_signaling::kClientSecretField] = conn_id;
+  device_info_reply[webrtc_signaling::kTypeField] =
+      webrtc_signaling::kDeviceInfoType;
+  device_info_reply[webrtc_signaling::kDeviceInfoField] =
+      device_handler->device_info();
+  Reply(device_info_reply);
+
+  return HttpStatusCode::Ok;
+}
+
+ForwardHandler::ForwardHandler(struct lws* wsi,
+                               PollConnectionStore* poll_store)
+    : ClientDynHandler(wsi, poll_store) {}
+
+HttpStatusCode ForwardHandler::DoPostInner(
+    std::shared_ptr<PollConnectionHandler> poll_handler,
+    const Json::Value& message) {
+  if (!poll_handler) {
+    ReplyError("Forward failed: No device associated to client");
+    return HttpStatusCode::Unauthorized;
+  }
+  auto client_id = poll_handler->client_id();
+  if (client_id == 0) {
+    ReplyError("Forward failed: No device associated to client");
+    return HttpStatusCode::Unauthorized;
+  }
+  if (!message.isMember(webrtc_signaling::kPayloadField)) {
+    ReplyError("Forward failed: No payload present in message");
+    return HttpStatusCode::BadRequest;
+  }
+  auto device_handler = poll_handler->device_handler();
+  if (!device_handler) {
+    ReplyError("Forward failed: Device disconnected");
+    return HttpStatusCode::NotFound;
+  }
+  device_handler->SendClientMessage(client_id,
+                                    message[webrtc_signaling::kPayloadField]);
+  // Don't waste an HTTP session returning nothing, send any pending device
+  // messages to the client instead.
+  return Poll(poll_handler);
+}
+
+PollHandler::PollHandler(struct lws* wsi, PollConnectionStore* poll_store)
+    : ClientDynHandler(wsi, poll_store) {}
+
+HttpStatusCode PollHandler::DoPostInner(
+    std::shared_ptr<PollConnectionHandler> poll_handler,
+    const Json::Value& /*message*/) {
+  return Poll(poll_handler);
+}
+
+ConfigHandler::ConfigHandler(struct lws* wsi, const ServerConfig& server_config)
+    : DynHandler(wsi), server_config_(server_config) {}
+
+HttpStatusCode ConfigHandler::DoGet() {
+  Json::Value reply = server_config_.ToJson();
+  reply[webrtc_signaling::kTypeField] = webrtc_signaling::kConfigType;
+  Json::StreamWriterBuilder factory;
+  auto replyAsString = Json::writeString(factory, reply);
+  AppendDataOut(replyAsString);
+  return HttpStatusCode::Ok;
+}
+
+HttpStatusCode ConfigHandler::DoPost() {
+  return HttpStatusCode::MethodNotAllowed;
 }
 
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc_operator/client_handler.h b/host/frontend/webrtc_operator/client_handler.h
index b10f3e8..2cb6a0e 100644
--- a/host/frontend/webrtc_operator/client_handler.h
+++ b/host/frontend/webrtc_operator/client_handler.h
@@ -27,18 +27,27 @@
 
 namespace cuttlefish {
 class DeviceHandler;
-class ClientHandler : public SignalHandler,
-                      public std::enable_shared_from_this<ClientHandler> {
+
+class ClientHandler {
  public:
-  ClientHandler(struct lws* wsi, DeviceRegistry* registry,
-                const ServerConfig& server_config);
-  void SendDeviceMessage(const Json::Value& message);
+  virtual ~ClientHandler() = default;
+  virtual void SendDeviceMessage(const Json::Value& message) = 0;
+};
+
+class ClientWSHandler : public ClientHandler,
+                        public SignalHandler,
+                        public std::enable_shared_from_this<ClientHandler> {
+ public:
+  ClientWSHandler(struct lws* wsi, DeviceRegistry* registry,
+                  const ServerConfig& server_config);
+
+  void SendDeviceMessage(const Json::Value& message) override;
 
   void OnClosed() override;
 
  protected:
   void handleMessage(const std::string& type,
-                    const Json::Value& message) override;
+                     const Json::Value& message) override;
 
  private:
   void handleConnectionRequest(const Json::Value& message);
@@ -50,14 +59,91 @@
   size_t client_id_;
 };
 
-class ClientHandlerFactory : public WebSocketHandlerFactory {
+class ClientWSHandlerFactory : public WebSocketHandlerFactory {
  public:
-  ClientHandlerFactory(DeviceRegistry* registry,
-                       const ServerConfig& server_config);
+  ClientWSHandlerFactory(DeviceRegistry* registry,
+                         const ServerConfig& server_config);
   std::shared_ptr<WebSocketHandler> Build(struct lws* wsi) override;
 
  private:
   DeviceRegistry* registry_;
   const ServerConfig& server_config_;
 };
+
+class PollConnectionHandler;
+
+class PollConnectionStore {
+  public:
+    PollConnectionStore() = default;
+
+    std::shared_ptr<PollConnectionHandler> Get(const std::string& conn_id) const;
+    std::string Add(std::shared_ptr<PollConnectionHandler> handler);
+  private:
+   std::map<std::string, std::shared_ptr<PollConnectionHandler>>
+       handlers_;
+};
+
+class ClientDynHandler : public DynHandler,
+                         public std::enable_shared_from_this<ClientHandler> {
+ public:
+  ClientDynHandler(struct lws* wsi, PollConnectionStore* poll_store);
+
+  HttpStatusCode DoGet() override;
+  HttpStatusCode DoPost() override;
+
+ protected:
+  virtual HttpStatusCode DoPostInner(std::shared_ptr<PollConnectionHandler>,
+                                     const Json::Value&) = 0;
+  // In the base class because it's shared by some of the subclasses
+  HttpStatusCode Poll(std::shared_ptr<PollConnectionHandler>);
+
+  void Reply(const Json::Value& json);
+  void ReplyError(const std::string& message);
+  bool ParseInput();
+
+  PollConnectionStore* poll_store_;
+};
+
+class ConnectHandler : public ClientDynHandler {
+ public:
+  ConnectHandler(struct lws* wsi, DeviceRegistry* registry,
+                 PollConnectionStore* poll_store);
+
+ protected:
+  HttpStatusCode DoPostInner(std::shared_ptr<PollConnectionHandler>,
+                             const Json::Value&) override;
+ private:
+  DeviceRegistry* registry_;
+};
+
+class ForwardHandler : public ClientDynHandler {
+ public:
+  ForwardHandler(struct lws* wsi, PollConnectionStore* poll_store);
+
+ protected:
+  HttpStatusCode DoPostInner(std::shared_ptr<PollConnectionHandler>,
+                             const Json::Value&) override;
+};
+
+class PollHandler : public ClientDynHandler {
+ public:
+  PollHandler(struct lws* wsi, PollConnectionStore* poll_store);
+
+ protected:
+  HttpStatusCode DoPostInner(std::shared_ptr<PollConnectionHandler>,
+                             const Json::Value&) override;
+};
+
+class ConfigHandler : public DynHandler {
+ public:
+  ConfigHandler(struct lws* wsi, const ServerConfig& server_config);
+
+  HttpStatusCode DoGet() override;
+
+  HttpStatusCode DoPost() override;
+
+ private:
+  const ServerConfig& server_config_;
+};
+
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc_operator/constants/signaling_constants.h b/host/frontend/webrtc_operator/constants/signaling_constants.h
index ecd3a3d..e03a8a8 100644
--- a/host/frontend/webrtc_operator/constants/signaling_constants.h
+++ b/host/frontend/webrtc_operator/constants/signaling_constants.h
@@ -24,6 +24,8 @@
 constexpr auto kClientIdField = "client_id";
 constexpr auto kPayloadField = "payload";
 constexpr auto kServersField = "ice_servers";
+constexpr auto kClientSecretField = "connection_id";
+constexpr auto kDevicePortField = "device_port";
 // These are defined in the IceServer dictionary
 constexpr auto kUrlsField = "urls";
 constexpr auto kUsernameField = "username";
@@ -38,6 +40,7 @@
 constexpr auto kClientMessageType = "client_msg";
 constexpr auto kClientDisconnectType = "client_disconnected";
 constexpr auto kDeviceMessageType = "device_msg";
+constexpr auto kPollType = "client_poll";
 
 }  // namespace webrtc_signaling
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc_operator/device_list_handler.cpp b/host/frontend/webrtc_operator/device_list_handler.cpp
index 314feea..c92e8e8 100644
--- a/host/frontend/webrtc_operator/device_list_handler.cpp
+++ b/host/frontend/webrtc_operator/device_list_handler.cpp
@@ -18,12 +18,10 @@
 namespace cuttlefish {
 
 DeviceListHandler::DeviceListHandler(struct lws* wsi,
-                                     const DeviceRegistry& registry)
-    : WebSocketHandler(wsi), registry_(registry) {}
+                                           DeviceRegistry& registry)
+    : DynHandler(wsi), registry_(registry) {}
 
-void DeviceListHandler::OnReceive(const uint8_t* /*msg*/, size_t /*len*/,
-                                  bool /*binary*/) {
-  // Ignore the message, just send the reply
+HttpStatusCode DeviceListHandler::DoGet() {
   Json::Value reply(Json::ValueType::arrayValue);
 
   for (const auto& id : registry_.ListDeviceIds()) {
@@ -31,18 +29,11 @@
   }
   Json::StreamWriterBuilder json_factory;
   auto replyAsString = Json::writeString(json_factory, reply);
-  EnqueueMessage(replyAsString.c_str(), replyAsString.size());
-  Close();
+  AppendDataOut(replyAsString);
+  return HttpStatusCode::Ok;
+}
+HttpStatusCode DeviceListHandler::DoPost() {
+  return HttpStatusCode::NotFound;
 }
 
-void DeviceListHandler::OnConnected() {}
-
-void DeviceListHandler::OnClosed() {}
-
-DeviceListHandlerFactory::DeviceListHandlerFactory(const DeviceRegistry& registry)
-  : registry_(registry) {}
-
-std::shared_ptr<WebSocketHandler> DeviceListHandlerFactory::Build(struct lws* wsi) {
-  return std::shared_ptr<WebSocketHandler>(new DeviceListHandler(wsi, registry_));
-}
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc_operator/device_list_handler.h b/host/frontend/webrtc_operator/device_list_handler.h
index 99d1f9c..2dc8406 100644
--- a/host/frontend/webrtc_operator/device_list_handler.h
+++ b/host/frontend/webrtc_operator/device_list_handler.h
@@ -25,24 +25,15 @@
 
 namespace cuttlefish {
 
-class DeviceListHandler : public WebSocketHandler {
+class DeviceListHandler : public DynHandler {
  public:
-  DeviceListHandler(struct lws* wsi, const DeviceRegistry& registry);
+  DeviceListHandler(struct lws* wsi, DeviceRegistry& registry);
 
-  void OnReceive(const uint8_t* msg, size_t len, bool binary) override;
-  void OnConnected() override;
-  void OnClosed() override;
+  HttpStatusCode DoGet() override;
+  HttpStatusCode DoPost() override;
 
  private:
-  const DeviceRegistry& registry_;
+  DeviceRegistry& registry_;
 };
 
-class DeviceListHandlerFactory : public WebSocketHandlerFactory {
- public:
-  DeviceListHandlerFactory(const DeviceRegistry& registry);
-  std::shared_ptr<WebSocketHandler> Build(struct lws* wsi) override;
-
- private:
-  const DeviceRegistry& registry_;
-};
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc_operator/server.cpp b/host/frontend/webrtc_operator/server.cpp
index c1f08f7..65295cf 100644
--- a/host/frontend/webrtc_operator/server.cpp
+++ b/host/frontend/webrtc_operator/server.cpp
@@ -41,7 +41,11 @@
 
 constexpr auto kRegisterDeviceUriPath = "/register_device";
 constexpr auto kConnectClientUriPath = "/connect_client";
-constexpr auto kListDevicesUriPath = "/list_devices";
+constexpr auto kListDevicesUriPath = "/devices";
+const constexpr auto kInfraConfigPath = "/infra_config";
+const constexpr auto kConnectPath = "/connect";
+const constexpr auto kForwardPath = "/forward";
+const constexpr auto kPollPath = "/poll_messages";
 
 }  // namespace
 
@@ -50,23 +54,59 @@
   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
 
   cuttlefish::DeviceRegistry device_registry;
+  cuttlefish::PollConnectionStore poll_store;
   cuttlefish::ServerConfig server_config({FLAGS_stun_server});
 
-  cuttlefish::WebSocketServer wss(
-        "webrtc-operator", FLAGS_certs_dir, FLAGS_assets_dir, FLAGS_http_server_port);
+  cuttlefish::WebSocketServer wss =
+      FLAGS_use_secure_http
+          ? cuttlefish::WebSocketServer("webrtc-operator", FLAGS_certs_dir,
+                                        FLAGS_assets_dir,
+                                        FLAGS_http_server_port)
+          : cuttlefish::WebSocketServer("webrtc-operator", FLAGS_assets_dir,
+                                        FLAGS_http_server_port);
 
+  // Device list endpoint
+  wss.RegisterDynHandlerFactory(
+      kListDevicesUriPath, [&device_registry](struct lws* wsi) {
+        return std::unique_ptr<cuttlefish::DynHandler>(
+            new cuttlefish::DeviceListHandler(wsi, device_registry));
+      });
+
+  // Websocket signaling endpoints
   auto device_handler_factory_p =
       std::unique_ptr<cuttlefish::WebSocketHandlerFactory>(
-          new cuttlefish::DeviceHandlerFactory(&device_registry, server_config));
-  wss.RegisterHandlerFactory(kRegisterDeviceUriPath, std::move(device_handler_factory_p));
+          new cuttlefish::DeviceHandlerFactory(&device_registry,
+                                               server_config));
+  wss.RegisterHandlerFactory(kRegisterDeviceUriPath,
+                             std::move(device_handler_factory_p));
   auto client_handler_factory_p =
       std::unique_ptr<cuttlefish::WebSocketHandlerFactory>(
-          new cuttlefish::ClientHandlerFactory(&device_registry, server_config));
-  wss.RegisterHandlerFactory(kConnectClientUriPath, std::move(client_handler_factory_p));
-  auto device_list_handler_factory_p =
-      std::unique_ptr<cuttlefish::WebSocketHandlerFactory>(
-          new cuttlefish::DeviceListHandlerFactory(device_registry));
-  wss.RegisterHandlerFactory(kListDevicesUriPath, std::move(device_list_handler_factory_p));
+          new cuttlefish::ClientWSHandlerFactory(&device_registry,
+                                                 server_config));
+  wss.RegisterHandlerFactory(kConnectClientUriPath,
+                             std::move(client_handler_factory_p));
+
+  // Polling signaling endpoints
+  wss.RegisterDynHandlerFactory(
+      kInfraConfigPath, [&server_config](struct lws* wsi) {
+        return std::unique_ptr<cuttlefish::DynHandler>(
+            new cuttlefish::ConfigHandler(wsi, server_config));
+      });
+  wss.RegisterDynHandlerFactory(
+      kConnectPath, [&device_registry, &poll_store](struct lws* wsi) {
+        return std::unique_ptr<cuttlefish::DynHandler>(
+            new cuttlefish::ConnectHandler(wsi, &device_registry, &poll_store));
+      });
+  wss.RegisterDynHandlerFactory(
+      kForwardPath, [&poll_store](struct lws* wsi) {
+        return std::unique_ptr<cuttlefish::DynHandler>(
+            new cuttlefish::ForwardHandler(wsi, &poll_store));
+      });
+  wss.RegisterDynHandlerFactory(
+      kPollPath, [&poll_store](struct lws* wsi) {
+        return std::unique_ptr<cuttlefish::DynHandler>(
+            new cuttlefish::PollHandler(wsi, &poll_store));
+      });
 
   wss.Serve();
   return 0;
diff --git a/host/libs/allocd/Android.bp b/host/libs/allocd/Android.bp
index 3c52507..c624817 100644
--- a/host/libs/allocd/Android.bp
+++ b/host/libs/allocd/Android.bp
@@ -41,6 +41,7 @@
         "resource.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
@@ -60,6 +61,7 @@
         "test/client.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libbase",
         "libcuttlefish_allocd_utils",
         "libcuttlefish_fs",
diff --git a/host/libs/audio_connector/server.cpp b/host/libs/audio_connector/server.cpp
index c153df9..4f0570c 100644
--- a/host/libs/audio_connector/server.cpp
+++ b/host/libs/audio_connector/server.cpp
@@ -335,7 +335,9 @@
   };
   std::vector<uint8_t> buffer(sizeof(vio_status) + size, 0);
   std::memcpy(buffer.data(), &vio_status, sizeof(vio_status));
-  std::memcpy(buffer.data() + sizeof(vio_status), data, size);
+  if (data) {
+    std::memcpy(buffer.data() + sizeof(vio_status), data, size);
+  }
   auto status_sent = control_socket_->Send(buffer.data(), buffer.size(), 0);
   if (status_sent < sizeof(vio_status) + size) {
     LOG(ERROR) << "Failed to send entire command status: "
diff --git a/host/libs/config/Android.bp b/host/libs/config/Android.bp
index 3697f4e..b3d0efb 100644
--- a/host/libs/config/Android.bp
+++ b/host/libs/config/Android.bp
@@ -21,10 +21,12 @@
     name: "libcuttlefish_host_config",
     srcs: [
         "bootconfig_args.cpp",
+        "config_flag.cpp",
         "custom_actions.cpp",
         "cuttlefish_config.cpp",
         "cuttlefish_config_instance.cpp",
         "data_image.cpp",
+        "feature.cpp",
         "fetcher_config.cpp",
         "host_tools_version.cpp",
         "kernel_args.cpp",
@@ -32,9 +34,11 @@
         "logging.cpp",
     ],
     shared_libs: [
+        "libext2_blkid",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libbase",
+        "libfruit",
         "libgflags",
         "libjsoncpp",
         "libz",
diff --git a/host/libs/config/adb/Android.bp b/host/libs/config/adb/Android.bp
new file mode 100644
index 0000000..c1b74de
--- /dev/null
+++ b/host/libs/config/adb/Android.bp
@@ -0,0 +1,64 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libcuttlefish_host_config_adb",
+    srcs: [
+        "config.cpp",
+        "data.cpp",
+        "flags.cpp",
+        "launch.cpp",
+        "strings.cpp",
+    ],
+    shared_libs: [
+        "libcuttlefish_fs",
+        "libcuttlefish_utils",
+        "libbase",
+        "libfruit",
+        "libgflags",
+        "libjsoncpp",
+        "libz",
+    ],
+    defaults: ["cuttlefish_host"],
+}
+
+cc_test_host {
+    name: "libcuttlefish_host_config_adb_test",
+    srcs: [
+        "test.cpp",
+    ],
+    static_libs: [
+        "libbase",
+        "libcuttlefish_fs",
+        "libcuttlefish_host_config",
+        "libcuttlefish_host_config_adb",
+        "libcuttlefish_utils",
+    ],
+    shared_libs: [
+        "libext2_blkid",
+        "libgflags",
+        "libfruit",
+        "libjsoncpp",
+        "liblog",
+    ],
+    defaults: ["cuttlefish_host"],
+    test_options: {
+        unit_test: true,
+    },
+}
diff --git a/host/libs/config/adb/adb.h b/host/libs/config/adb/adb.h
new file mode 100644
index 0000000..08b1549
--- /dev/null
+++ b/host/libs/config/adb/adb.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <fruit/fruit.h>
+#include <set>
+
+#include "host/libs/config/command_source.h"
+#include "host/libs/config/config_flag.h"
+#include "host/libs/config/config_fragment.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
+#include "host/libs/config/kernel_log_pipe_provider.h"
+
+namespace cuttlefish {
+
+enum class AdbMode {
+  VsockTunnel,
+  VsockHalfTunnel,
+  NativeVsock,
+  Unknown,
+};
+
+AdbMode StringToAdbMode(const std::string& mode);
+std::string AdbModeToString(AdbMode mode);
+
+class AdbConfig {
+ public:
+  virtual ~AdbConfig() = default;
+  virtual const std::set<AdbMode>& Modes() const = 0;
+  virtual bool SetModes(const std::set<AdbMode>&) = 0;
+  virtual bool SetModes(std::set<AdbMode>&&) = 0;
+
+  virtual bool RunConnector() const = 0;
+  virtual bool SetRunConnector(bool) = 0;
+};
+
+class AdbConfigFragment : public ConfigFragment {};
+class AdbConfigFlag : public FlagFeature {};
+
+fruit::Component<AdbConfig> AdbConfigComponent();
+fruit::Component<fruit::Required<AdbConfig, ConfigFlag>, AdbConfigFlag>
+AdbConfigFlagComponent();
+fruit::Component<fruit::Required<AdbConfig>, AdbConfigFragment>
+AdbConfigFragmentComponent();
+fruit::Component<fruit::Required<KernelLogPipeProvider, const AdbConfig,
+                                 const CuttlefishConfig::InstanceSpecific>>
+LaunchAdbComponent();
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/adb/config.cpp b/host/libs/config/adb/config.cpp
new file mode 100644
index 0000000..6fc6378
--- /dev/null
+++ b/host/libs/config/adb/config.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/libs/config/adb/adb.h"
+
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+#include <json/json.h>
+
+#include "host/libs/config/config_fragment.h"
+
+namespace cuttlefish {
+namespace {
+
+class AdbConfigFragmentImpl : public AdbConfigFragment {
+ public:
+  INJECT(AdbConfigFragmentImpl(AdbConfig& config)) : config_(config) {}
+
+  std::string Name() const override { return "AdbConfigFragmentImpl"; }
+
+  Json::Value Serialize() const override {
+    Json::Value json;
+    json[kMode] = Json::Value(Json::arrayValue);
+    for (const auto& mode : config_.Modes()) {
+      json[kMode].append(AdbModeToString(mode));
+    }
+    json[kConnectorEnabled] = config_.RunConnector();
+    return json;
+  }
+  bool Deserialize(const Json::Value& json) override {
+    if (!json.isMember(kMode) || json[kMode].type() != Json::arrayValue) {
+      LOG(ERROR) << "Invalid value for " << kMode;
+      return false;
+    }
+    std::set<AdbMode> modes;
+    for (auto& mode : json[kMode]) {
+      if (mode.type() != Json::stringValue) {
+        LOG(ERROR) << "Invalid mode type" << mode;
+        return false;
+      }
+      modes.insert(StringToAdbMode(mode.asString()));
+    }
+    if (!config_.SetModes(std::move(modes))) {
+      LOG(ERROR) << "Failed to set adb modes";
+      return false;
+    }
+
+    if (!json.isMember(kConnectorEnabled) ||
+        json[kConnectorEnabled].type() != Json::booleanValue) {
+      LOG(ERROR) << "Invalid value for " << kConnectorEnabled;
+      return false;
+    }
+    if (!config_.SetRunConnector(json[kConnectorEnabled].asBool())) {
+      LOG(ERROR) << "Failed to set whether to run the adb connector";
+    }
+    return true;
+  }
+
+ private:
+  static constexpr char kMode[] = "mode";
+  static constexpr char kConnectorEnabled[] = "connector_enabled";
+  AdbConfig& config_;
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<AdbConfig>, AdbConfigFragment>
+AdbConfigFragmentComponent() {
+  return fruit::createComponent()
+      .bind<AdbConfigFragment, AdbConfigFragmentImpl>()
+      .addMultibinding<ConfigFragment, AdbConfigFragment>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/adb/data.cpp b/host/libs/config/adb/data.cpp
new file mode 100644
index 0000000..81db3e9
--- /dev/null
+++ b/host/libs/config/adb/data.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/libs/config/adb/adb.h"
+
+#include <fruit/fruit.h>
+#include <set>
+
+namespace cuttlefish {
+namespace {}
+
+class AdbConfigImpl : public AdbConfig {
+ public:
+  INJECT(AdbConfigImpl()) {}
+
+  const std::set<AdbMode>& Modes() const override { return modes_; }
+  bool SetModes(const std::set<AdbMode>& modes) override {
+    modes_ = modes;
+    return true;
+  }
+  bool SetModes(std::set<AdbMode>&& modes) override {
+    modes_ = std::move(modes);
+    return true;
+  }
+
+  bool RunConnector() const override { return run_connector_; }
+  bool SetRunConnector(bool run) override {
+    run_connector_ = run;
+    return true;
+  }
+
+ private:
+  std::set<AdbMode> modes_;
+  bool run_connector_;
+};
+
+fruit::Component<AdbConfig> AdbConfigComponent() {
+  return fruit::createComponent().bind<AdbConfig, AdbConfigImpl>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/adb/flags.cpp b/host/libs/config/adb/flags.cpp
new file mode 100644
index 0000000..56451f7
--- /dev/null
+++ b/host/libs/config/adb/flags.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/libs/config/adb/adb.h"
+
+#include <android-base/strings.h>
+
+#include "common/libs/utils/flag_parser.h"
+#include "host/libs/config/config_flag.h"
+#include "host/libs/config/feature.h"
+
+namespace cuttlefish {
+namespace {
+
+class AdbConfigFlagImpl : public AdbConfigFlag {
+ public:
+  INJECT(AdbConfigFlagImpl(AdbConfig& config, ConfigFlag& config_flag))
+      : config_(config), config_flag_(config_flag) {
+    mode_flag_ = GflagsCompatFlag("adb_mode").Help(mode_help);
+    mode_flag_.Getter([this]() {
+      std::stringstream modes;
+      for (const auto& mode : config_.Modes()) {
+        modes << "," << AdbModeToString(mode);
+      }
+      return modes.str().substr(1);  // First comma
+    });
+    mode_flag_.Setter([this](const FlagMatch& match) {
+      // TODO(schuffelen): Error on unknown types?
+      std::set<AdbMode> modes;
+      for (auto& mode : android::base::Split(match.value, ",")) {
+        modes.insert(StringToAdbMode(mode));
+      }
+      return config_.SetModes(modes);
+    });
+  }
+
+  std::string Name() const override { return "AdbConfigFlagImpl"; }
+
+  std::unordered_set<FlagFeature*> Dependencies() const override {
+    return {static_cast<FlagFeature*>(&config_flag_)};
+  }
+
+  bool Process(std::vector<std::string>& args) override {
+    // Defaults
+    config_.SetModes({AdbMode::VsockHalfTunnel});
+    bool run_adb_connector = !IsRunningInContainer();
+    Flag run_flag = GflagsCompatFlag("run_adb_connector", run_adb_connector);
+    if (!ParseFlags({run_flag, mode_flag_}, args)) {
+      LOG(ERROR) << "Failed to parse adb config flags";
+      return false;
+    }
+    config_.SetRunConnector(run_adb_connector);
+
+    auto adb_modes_check = config_.Modes();
+    adb_modes_check.erase(AdbMode::Unknown);
+    if (adb_modes_check.size() < 1) {
+      LOG(INFO) << "ADB not enabled";
+    }
+
+    return true;
+  }
+  bool WriteGflagsCompatHelpXml(std::ostream& out) const override {
+    bool run = config_.RunConnector();
+    Flag run_flag = GflagsCompatFlag("run_adb_connector", run).Help(run_help);
+    return WriteGflagsCompatXml({run_flag, mode_flag_}, out);
+  }
+
+ private:
+  static constexpr char run_help[] =
+      "Maintain adb connection by sending 'adb connect' commands to the "
+      "server. Only relevant with -adb_mode=tunnel or vsock_tunnel.";
+  static constexpr char mode_help[] =
+      "Mode for ADB connection."
+      "'vsock_tunnel' for a TCP connection tunneled through vsock, "
+      "'native_vsock' for a  direct connection to the guest ADB over "
+      "vsock, 'vsock_half_tunnel' for a TCP connection forwarded to "
+      "the guest ADB server, or a comma separated list of types as in "
+      "'native_vsock,vsock_half_tunnel'";
+
+  AdbConfig& config_;
+  ConfigFlag& config_flag_;
+  Flag mode_flag_;
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<AdbConfig, ConfigFlag>, AdbConfigFlag>
+AdbConfigFlagComponent() {
+  return fruit::createComponent()
+      .bind<AdbConfigFlag, AdbConfigFlagImpl>()
+      .addMultibinding<FlagFeature, AdbConfigFlag>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/adb/launch.cpp b/host/libs/config/adb/launch.cpp
new file mode 100644
index 0000000..65f439b
--- /dev/null
+++ b/host/libs/config/adb/launch.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/libs/config/adb/adb.h"
+
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
+
+namespace cuttlefish {
+namespace {
+
+class AdbHelper {
+ public:
+  INJECT(AdbHelper(const CuttlefishConfig::InstanceSpecific& instance,
+                   const AdbConfig& config))
+      : instance_(instance), config_(config) {}
+
+  bool ModeEnabled(const AdbMode& mode) const {
+    return config_.Modes().count(mode) > 0;
+  }
+
+  std::string ConnectorTcpArg() const {
+    return "0.0.0.0:" + std::to_string(instance_.adb_host_port());
+  }
+
+  std::string ConnectorVsockArg() const {
+    return "vsock:" + std::to_string(instance_.vsock_guest_cid()) + ":5555";
+  }
+
+  bool VsockTunnelEnabled() const {
+    return instance_.vsock_guest_cid() > 2 && ModeEnabled(AdbMode::VsockTunnel);
+  }
+
+  bool VsockHalfTunnelEnabled() const {
+    return instance_.vsock_guest_cid() > 2 &&
+           ModeEnabled(AdbMode::VsockHalfTunnel);
+  }
+
+  bool TcpConnectorEnabled() const {
+    bool vsock_tunnel = VsockTunnelEnabled();
+    bool vsock_half_tunnel = VsockHalfTunnelEnabled();
+    return config_.RunConnector() && (vsock_tunnel || vsock_half_tunnel);
+  }
+
+  bool VsockConnectorEnabled() const {
+    return config_.RunConnector() && ModeEnabled(AdbMode::NativeVsock);
+  }
+
+ private:
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  const AdbConfig& config_;
+};
+
+class AdbConnector : public CommandSource {
+ public:
+  INJECT(AdbConnector(const AdbHelper& helper)) : helper_(helper) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    Command console_forwarder_cmd(ConsoleForwarderBinary());
+    Command adb_connector(AdbConnectorBinary());
+    std::set<std::string> addresses;
+
+    if (helper_.TcpConnectorEnabled()) {
+      addresses.insert(helper_.ConnectorTcpArg());
+    }
+    if (helper_.VsockConnectorEnabled()) {
+      addresses.insert(helper_.ConnectorVsockArg());
+    }
+
+    if (addresses.size() == 0) {
+      return {};
+    }
+    std::string address_arg = "--addresses=";
+    for (auto& arg : addresses) {
+      address_arg += arg + ",";
+    }
+    address_arg.pop_back();
+    adb_connector.AddParameter(address_arg);
+    std::vector<Command> commands;
+    commands.emplace_back(std::move(adb_connector));
+    return std::move(commands);
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "AdbConnector"; }
+  bool Enabled() const override {
+    return helper_.TcpConnectorEnabled() || helper_.VsockConnectorEnabled();
+  }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override { return true; }
+
+  const AdbHelper& helper_;
+};
+
+class SocketVsockProxy : public CommandSource {
+ public:
+  INJECT(SocketVsockProxy(const AdbHelper& helper,
+                          const CuttlefishConfig::InstanceSpecific& instance,
+                          KernelLogPipeProvider& log_pipe_provider))
+      : helper_(helper),
+        instance_(instance),
+        log_pipe_provider_(log_pipe_provider) {}
+
+  // CommandSource
+  std::vector<Command> Commands() override {
+    std::vector<Command> commands;
+    if (helper_.VsockTunnelEnabled()) {
+      Command adb_tunnel(SocketVsockProxyBinary());
+      adb_tunnel.AddParameter("-adbd_events_fd=", kernel_log_pipe_);
+      /**
+       * This socket_vsock_proxy (a.k.a. sv proxy) runs on the host. It assumes
+       * that another sv proxy runs inside the guest. see:
+       * shared/config/init.vendor.rc The sv proxy in the guest exposes
+       * vsock:cid:6520 across the cuttlefish instances in multi-tenancy. cid is
+       * different per instance.
+       *
+       * This host sv proxy should cooperate with the guest sv proxy. Thus, one
+       * end of the tunnel is vsock:cid:6520 regardless of instance number.
+       * Another end faces the host adb daemon via tcp. Thus, the server type is
+       * tcp here. The tcp port differs from instance to instance, and is
+       * instance.adb_host_port()
+       *
+       */
+      adb_tunnel.AddParameter("--server=tcp");
+      adb_tunnel.AddParameter("--vsock_port=6520");
+      adb_tunnel.AddParameter("--server_fd=", tcp_server_);
+      adb_tunnel.AddParameter("--vsock_cid=", instance_.vsock_guest_cid());
+      commands.emplace_back(std::move(adb_tunnel));
+    }
+    if (helper_.VsockHalfTunnelEnabled()) {
+      Command adb_tunnel(SocketVsockProxyBinary());
+      adb_tunnel.AddParameter("-adbd_events_fd=", kernel_log_pipe_);
+      /*
+       * This socket_vsock_proxy (a.k.a. sv proxy) runs on the host, and
+       * cooperates with the adbd inside the guest. See this file:
+       *  shared/device.mk, especially the line says "persist.adb.tcp.port="
+       *
+       * The guest adbd is listening on vsock:cid:5555 across cuttlefish
+       * instances. Sv proxy faces the host adb daemon via tcp. The server type
+       * should be therefore tcp, and the port should differ from instance to
+       * instance and be equal to instance.adb_host_port()
+       */
+      adb_tunnel.AddParameter("--server=tcp");
+      adb_tunnel.AddParameter("--vsock_port=", 5555);
+      adb_tunnel.AddParameter("--server_fd=", tcp_server_);
+      adb_tunnel.AddParameter("--vsock_cid=", instance_.vsock_guest_cid());
+      commands.emplace_back(std::move(adb_tunnel));
+    }
+    return commands;
+  }
+
+  // SetupFeature
+  std::string Name() const override { return "SocketVsockProxy"; }
+  bool Enabled() const override {
+    return helper_.VsockTunnelEnabled() || helper_.VsockHalfTunnelEnabled();
+  }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override {
+    return {static_cast<SetupFeature*>(&log_pipe_provider_)};
+  }
+  bool Setup() override {
+    tcp_server_ =
+        SharedFD::SocketLocalServer(instance_.adb_host_port(), SOCK_STREAM);
+    if (!tcp_server_->IsOpen()) {
+      LOG(ERROR) << "Unable to create socket_vsock_proxy server socket: "
+                 << tcp_server_->StrError();
+      return false;
+    }
+    kernel_log_pipe_ = log_pipe_provider_.KernelLogPipe();
+    return true;
+  }
+
+  const AdbHelper& helper_;
+  const CuttlefishConfig::InstanceSpecific& instance_;
+  KernelLogPipeProvider& log_pipe_provider_;
+  SharedFD kernel_log_pipe_;
+  SharedFD tcp_server_;
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<KernelLogPipeProvider, const AdbConfig,
+                                 const CuttlefishConfig::InstanceSpecific>>
+LaunchAdbComponent() {
+  return fruit::createComponent()
+      .addMultibinding<CommandSource, AdbConnector>()
+      .addMultibinding<CommandSource, SocketVsockProxy>()
+      .addMultibinding<SetupFeature, AdbConnector>()
+      .addMultibinding<SetupFeature, SocketVsockProxy>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/adb/strings.cpp b/host/libs/config/adb/strings.cpp
new file mode 100644
index 0000000..4de9e23
--- /dev/null
+++ b/host/libs/config/adb/strings.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/libs/config/adb/adb.h"
+
+#include <algorithm>
+#include <string>
+
+namespace cuttlefish {
+
+AdbMode StringToAdbMode(const std::string& mode_cased) {
+  std::string mode = mode_cased;
+  std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower);
+  if (mode == "vsock_tunnel") {
+    return AdbMode::VsockTunnel;
+  } else if (mode == "vsock_half_tunnel") {
+    return AdbMode::VsockHalfTunnel;
+  } else if (mode == "native_vsock") {
+    return AdbMode::NativeVsock;
+  } else {
+    return AdbMode::Unknown;
+  }
+}
+
+std::string AdbModeToString(AdbMode mode) {
+  switch (mode) {
+    case AdbMode::VsockTunnel:
+      return "vsock_tunnel";
+    case AdbMode::VsockHalfTunnel:
+      return "vsock_half_tunnel";
+    case AdbMode::NativeVsock:
+      return "native_vsock";
+    case AdbMode::Unknown:  // fall through
+    default:
+      return "unknown";
+  }
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/adb/test.cpp b/host/libs/config/adb/test.cpp
new file mode 100644
index 0000000..3250568
--- /dev/null
+++ b/host/libs/config/adb/test.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fruit/fruit.h>
+#include <gtest/gtest.h>
+#include <host/libs/config/adb/adb.h>
+
+#include <string>
+
+#include "host/libs/config/feature.h"
+
+namespace cuttlefish {
+
+struct TestData {
+  INJECT(TestData(AdbConfig& config, AdbConfigFragment& fragment))
+      : config(config), fragment(fragment) {}
+
+  AdbConfig& config;
+  AdbConfigFragment& fragment;
+};
+
+fruit::Component<TestData> TestComponent() {
+  return fruit::createComponent()
+      .install(AdbConfigComponent)
+      .install(AdbConfigFlagComponent)
+      .install(AdbConfigFragmentComponent)
+      .install(ConfigFlagPlaceholder);
+}
+
+TEST(AdbConfigTest, SetFromFlags) {
+  fruit::Injector<TestData> injector(TestComponent);
+  TestData& data = injector.get<TestData&>();
+  std::vector<std::string> args = {
+      "--adb_mode=vsock_tunnel,vsock_half_tunnel,native_vsock,unknown",
+      "--run_adb_connector=false",
+  };
+  auto flags = injector.getMultibindings<FlagFeature>();
+  auto processed = FlagFeature::ProcessFlags(flags, args);
+  ASSERT_TRUE(processed.ok()) << processed.error();
+  ASSERT_TRUE(args.empty());
+
+  std::set<AdbMode> modes = {AdbMode::VsockTunnel, AdbMode::VsockHalfTunnel,
+                             AdbMode::NativeVsock, AdbMode::Unknown};
+  ASSERT_EQ(data.config.Modes(), modes);
+  ASSERT_FALSE(data.config.RunConnector());
+}
+
+TEST(AdbConfigTest, SerializeDeserialize) {
+  fruit::Injector<TestData> injector1(TestComponent);
+  TestData& data1 = injector1.get<TestData&>();
+  ASSERT_TRUE(
+      data1.config.SetModes({AdbMode::VsockTunnel, AdbMode::VsockHalfTunnel,
+                             AdbMode::NativeVsock, AdbMode::Unknown}));
+  ASSERT_TRUE(data1.config.SetRunConnector(false));
+
+  fruit::Injector<TestData> injector2(TestComponent);
+  TestData& data2 = injector2.get<TestData&>();
+  ASSERT_TRUE(data2.fragment.Deserialize(data1.fragment.Serialize()));
+  ASSERT_EQ(data1.config.Modes(), data2.config.Modes());
+  ASSERT_EQ(data1.config.RunConnector(), data2.config.RunConnector());
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/bootconfig_args.cpp b/host/libs/config/bootconfig_args.cpp
index 1f926eb..00673f6 100644
--- a/host/libs/config/bootconfig_args.cpp
+++ b/host/libs/config/bootconfig_args.cpp
@@ -24,6 +24,7 @@
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
 #include "host/libs/vm_manager/qemu_manager.h"
 #include "host/libs/vm_manager/vm_manager.h"
@@ -47,15 +48,6 @@
   return os.str();
 }
 
-std::string mac_to_str(const std::array<unsigned char, 6>& mac) {
-  std::ostringstream stream;
-  stream << std::hex << (int)mac[0];
-  for (int i = 1; i < 6; i++) {
-    stream << ":" << std::hex << (int)mac[i];
-  }
-  return stream.str();
-}
-
 // TODO(schuffelen): Move more of this into host/libs/vm_manager, as a
 // substitute for the vm_manager comparisons.
 std::vector<std::string> VmManagerBootconfig(const CuttlefishConfig& config) {
@@ -86,7 +78,7 @@
   auto vmm = vm_manager::GetVmManager(config.vm_manager(), config.target_arch());
   bootconfig_args.push_back(
       vmm->ConfigureBootDevices(instance.virtual_disk_paths().size()));
-  AppendVector(&bootconfig_args, vmm->ConfigureGpuMode(config.gpu_mode()));
+  AppendVector(&bootconfig_args, vmm->ConfigureGraphics(config));
 
   bootconfig_args.push_back(
       concat("androidboot.serialno=", instance.serial_number()));
@@ -108,6 +100,11 @@
                                      instance.tombstone_receiver_port()));
   }
 
+  if (instance.confui_host_vsock_port()) {
+    bootconfig_args.push_back(concat("androidboot.vsock_confirmationui_port=",
+                                     instance.confui_host_vsock_port()));
+  }
+
   if (instance.config_server_port()) {
     bootconfig_args.push_back(
         concat("androidboot.cuttlefish_config_server_port=",
@@ -126,7 +123,7 @@
 
   if (config.enable_vehicle_hal_grpc_server() &&
       instance.vehicle_hal_server_port() &&
-      FileExists(config.vehicle_hal_grpc_server_binary())) {
+      FileExists(VehicleHalGrpcServerBinary())) {
     constexpr int vehicle_hal_server_cid = 2;
     bootconfig_args.push_back(concat(
         "androidboot.vendor.vehiclehal.server.cid=", vehicle_hal_server_cid));
@@ -144,11 +141,6 @@
                instance.audiocontrol_server_port()));
   }
 
-  if (instance.frames_server_port()) {
-    bootconfig_args.push_back(concat("androidboot.vsock_frames_port=",
-                                     instance.frames_server_port()));
-  }
-
   if (instance.camera_server_port()) {
     bootconfig_args.push_back(concat("androidboot.vsock_camera_port=",
                                      instance.camera_server_port()));
@@ -162,11 +154,11 @@
                                      instance.modem_simulator_ports()));
   }
 
-  // TODO(b/158131610): Set this in crosvm instead
-  bootconfig_args.push_back(concat("androidboot.wifi_mac_address=",
-                                   mac_to_str(instance.wifi_mac_address())));
+  bootconfig_args.push_back(concat("androidboot.fstab_suffix=",
+                                   config.userdata_format()));
 
-  bootconfig_args.push_back("androidboot.verifiedbootstate=orange");
+  bootconfig_args.push_back(
+      concat("androidboot.wifi_mac_prefix=", instance.wifi_mac_prefix()));
 
   // Non-native architecture implies a significantly slower execution speed, so
   // set a large timeout multiplier.
@@ -174,7 +166,17 @@
     bootconfig_args.push_back("androidboot.hw_timeout_multiplier=50");
   }
 
-  // TODO(b/173815685): Create an extra_bootconfig flag and add it to bootconfig
+  // TODO(b/217564326): improve this checks for a hypervisor in the VM.
+  if (config.target_arch() == Arch::X86 ||
+      config.target_arch() == Arch::X86_64) {
+    bootconfig_args.push_back(
+        concat("androidboot.hypervisor.version=cf-", config.vm_manager()));
+    bootconfig_args.push_back("androidboot.hypervisor.vm.supported=1");
+    bootconfig_args.push_back(
+        "androidboot.hypervisor.protected_vm.supported=0");
+  }
+
+  AppendVector(&bootconfig_args, config.extra_bootconfig_args());
 
   return bootconfig_args;
 }
diff --git a/guest/hals/audio/Android.bp b/host/libs/config/command_source.h
similarity index 62%
rename from guest/hals/audio/Android.bp
rename to host/libs/config/command_source.h
index c6faae7..c4e3b23 100644
--- a/guest/hals/audio/Android.bp
+++ b/host/libs/config/command_source.h
@@ -1,3 +1,4 @@
+//
 // Copyright (C) 2019 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,16 +13,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
+#pragma once
 
-cc_library_shared {
-    name: "audio.primary.cutf",
-    relative_install_path: "hw",
-    defaults: ["cuttlefish_guest_only"],
-    vendor: true,
-    srcs: ["audio_hw.c"],
-    cflags: ["-Wno-unused-parameter"],
-    shared_libs: ["libcutils", "libhardware", "liblog", "libtinyalsa"],
-}
+#include <fruit/fruit.h>
+#include <vector>
+
+#include "common/libs/utils/subprocess.h"
+#include "host/libs/config/feature.h"
+
+namespace cuttlefish {
+
+class CommandSource : public virtual SetupFeature {
+ public:
+  virtual ~CommandSource() = default;
+  virtual std::vector<Command> Commands() = 0;
+};
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/config_flag.cpp b/host/libs/config/config_flag.cpp
new file mode 100644
index 0000000..d7b5f00
--- /dev/null
+++ b/host/libs/config/config_flag.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/config/config_flag.h"
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <gflags/gflags.h>
+#include <json/json.h>
+#include <fstream>
+#include <set>
+#include <string>
+
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
+#include "host/libs/config/cuttlefish_config.h"
+
+// To support other files that use this from gflags.
+DEFINE_string(system_image_dir, "", "");
+
+using gflags::FlagSettingMode::SET_FLAGS_DEFAULT;
+
+namespace cuttlefish {
+
+namespace {
+
+class SystemImageDirFlagImpl : public SystemImageDirFlag {
+ public:
+  INJECT(SystemImageDirFlagImpl()) {
+    auto help = "Location of the system partition images.";
+    flag_ = GflagsCompatFlag("system_image_dir", path_).Help(help);
+  }
+  const std::string& Path() override { return path_; }
+
+  std::string Name() const override { return "SystemImageDirFlagImpl"; }
+  std::unordered_set<FlagFeature*> Dependencies() const override { return {}; }
+  bool Process(std::vector<std::string>& args) override {
+    path_ = DefaultGuestImagePath("");
+    if (!flag_.Parse(args)) {
+      return false;
+    }
+    // To support other files that use this from gflags.
+    FLAGS_system_image_dir = path_;
+    gflags::SetCommandLineOptionWithMode("system_image_dir", path_.c_str(),
+                                         SET_FLAGS_DEFAULT);
+    return true;
+  }
+  bool WriteGflagsCompatHelpXml(std::ostream&) const override {
+    // TODO(schuffelen): Write something here when this is removed from gflags
+    return true;
+  }
+
+ private:
+  std::string path_;
+  Flag flag_;
+};
+
+class ConfigReader : public FlagFeature {
+ public:
+  INJECT(ConfigReader()) = default;
+
+  bool HasConfig(const std::string& name) const {
+    return allowed_config_presets_.count(name) > 0;
+  }
+  const std::set<std::string>& AvailableConfigs() const {
+    return allowed_config_presets_;
+  }
+  std::optional<Json::Value> ReadConfig(const std::string& name) const {
+    auto path =
+        DefaultHostArtifactsPath("etc/cvd_config/cvd_config_" + name + ".json");
+    Json::Value config;
+    Json::CharReaderBuilder builder;
+    std::ifstream ifs(path);
+    std::string errorMessage;
+    if (!Json::parseFromStream(builder, ifs, &config, &errorMessage)) {
+      LOG(ERROR) << "Could not read config file " << path << ": "
+                 << errorMessage;
+      return {};
+    }
+    return config;
+  }
+
+  // FlagFeature
+  std::string Name() const override { return "ConfigReader"; }
+  std::unordered_set<FlagFeature*> Dependencies() const override { return {}; }
+  bool Process(std::vector<std::string>&) override {
+    for (const std::string& file :
+         DirectoryContents(DefaultHostArtifactsPath("etc/cvd_config"))) {
+      std::string_view local_file(file);
+      if (android::base::ConsumePrefix(&local_file, "cvd_config_") &&
+          android::base::ConsumeSuffix(&local_file, ".json")) {
+        allowed_config_presets_.emplace(local_file);
+      }
+    }
+    return true;
+  }
+  bool WriteGflagsCompatHelpXml(std::ostream&) const override { return true; }
+
+ private:
+  std::set<std::string> allowed_config_presets_;
+};
+
+class ConfigFlagImpl : public ConfigFlag {
+ public:
+  INJECT(ConfigFlagImpl(ConfigReader& cr, SystemImageDirFlag& s))
+      : config_reader_(cr), system_image_dir_flag_(s) {
+    is_default_ = true;
+    config_ = "phone";  // default value
+    auto help =
+        "Config preset name. Will automatically set flag fields using the "
+        "values from this file of presets. See "
+        "device/google/cuttlefish/shared/config/config_*.json for possible "
+        "values.";
+    auto getter = [this]() { return config_; };
+    auto setter = [this](const FlagMatch& m) { return ChooseConfig(m.value); };
+    flag_ = GflagsCompatFlag("config").Help(help).Getter(getter).Setter(setter);
+  }
+
+  std::string Name() const override { return "ConfigFlagImpl"; }
+  std::unordered_set<FlagFeature*> Dependencies() const override {
+    return {
+        static_cast<FlagFeature*>(&config_reader_),
+        static_cast<FlagFeature*>(&system_image_dir_flag_),
+    };
+  }
+  bool Process(std::vector<std::string>& args) override {
+    if (!flag_.Parse(args)) {
+      LOG(ERROR) << "Failed to parse `--config` flag";
+      return false;
+    }
+
+    if (auto info_cfg = FindAndroidInfoConfig(); is_default_ && info_cfg) {
+      config_ = *info_cfg;
+    }
+    LOG(INFO) << "Launching CVD using --config='" << config_ << "'.";
+    auto config_values = config_reader_.ReadConfig(config_);
+    if (!config_values) {
+      LOG(ERROR) << "Failed to read config for " << config_;
+      return false;
+    }
+    for (const std::string& flag : config_values->getMemberNames()) {
+      std::string value;
+      if (flag == "custom_actions") {
+        Json::StreamWriterBuilder factory;
+        value = Json::writeString(factory, (*config_values)[flag]);
+      } else {
+        value = (*config_values)[flag].asString();
+      }
+      args.insert(args.begin(), "--" + flag + "=" + value);
+      // To avoid the flag forwarder from thinking this song is different from a
+      // default. Should fail silently if the flag doesn't exist.
+      gflags::SetCommandLineOptionWithMode(flag.c_str(), value.c_str(),
+                                           SET_FLAGS_DEFAULT);
+    }
+    return true;
+  }
+  bool WriteGflagsCompatHelpXml(std::ostream& out) const override {
+    return flag_.WriteGflagsCompatXml(out);
+  }
+
+ private:
+  bool ChooseConfig(const std::string& name) {
+    if (!config_reader_.HasConfig(name)) {
+      LOG(ERROR) << "Invalid --config option '" << name << "'. Valid options: "
+                 << android::base::Join(config_reader_.AvailableConfigs(), ",");
+      return false;
+    }
+    config_ = name;
+    is_default_ = false;
+    return true;
+  }
+  std::optional<std::string> FindAndroidInfoConfig() const {
+    auto info_path = system_image_dir_flag_.Path() + "/android-info.txt";
+    if (!FileExists(info_path)) {
+      return {};
+    }
+    std::ifstream ifs{info_path};
+    if (!ifs.is_open()) {
+      return {};
+    }
+    std::string android_info;
+    ifs >> android_info;
+    std::string_view local_android_info(android_info);
+    if (!android::base::ConsumePrefix(&local_android_info, "config=")) {
+      return {};
+    }
+    if (!config_reader_.HasConfig(std::string{local_android_info})) {
+      LOG(WARNING) << info_path << " contains invalid config preset: '"
+                   << local_android_info << "'.";
+      return {};
+    }
+    return std::string{local_android_info};
+  }
+
+  ConfigReader& config_reader_;
+  SystemImageDirFlag& system_image_dir_flag_;
+  std::string config_;
+  bool is_default_;
+  Flag flag_;
+};
+
+class ConfigFlagPlaceholderImpl : public ConfigFlag {
+ public:
+  INJECT(ConfigFlagPlaceholderImpl()) {}
+
+  std::string Name() const override { return "ConfigFlagPlaceholderImpl"; }
+  std::unordered_set<FlagFeature*> Dependencies() const override { return {}; }
+  bool Process(std::vector<std::string>&) override { return true; }
+  bool WriteGflagsCompatHelpXml(std::ostream&) const override { return true; }
+};
+
+}  // namespace
+
+fruit::Component<SystemImageDirFlag, ConfigFlag> ConfigFlagComponent() {
+  return fruit::createComponent()
+      .addMultibinding<FlagFeature, ConfigReader>()
+      .bind<ConfigFlag, ConfigFlagImpl>()
+      .addMultibinding<FlagFeature, ConfigFlag>()
+      .bind<SystemImageDirFlag, SystemImageDirFlagImpl>()
+      .addMultibinding<FlagFeature, SystemImageDirFlag>();
+}
+
+fruit::Component<ConfigFlag> ConfigFlagPlaceholder() {
+  return fruit::createComponent()
+      .addMultibinding<FlagFeature, ConfigFlag>()
+      .bind<ConfigFlag, ConfigFlagPlaceholderImpl>();
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/config_flag.h b/host/libs/config/config_flag.h
new file mode 100644
index 0000000..04258eb
--- /dev/null
+++ b/host/libs/config/config_flag.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <fruit/fruit.h>
+#include <string>
+
+#include "host/libs/config/feature.h"
+
+namespace cuttlefish {
+
+// TODO(schuffelen): Move this to a more central location?
+class SystemImageDirFlag : public FlagFeature {
+ public:
+  virtual const std::string& Path() = 0;
+};
+
+class ConfigFlag : public FlagFeature {};
+
+fruit::Component<SystemImageDirFlag, ConfigFlag> ConfigFlagComponent();
+
+fruit::Component<ConfigFlag> ConfigFlagPlaceholder();
+
+}  // namespace cuttlefish
diff --git a/common/libs/utils/size_utils.cpp b/host/libs/config/config_fragment.h
similarity index 64%
copy from common/libs/utils/size_utils.cpp
copy to host/libs/config/config_fragment.h
index 9f25445..cc1bfc1 100644
--- a/common/libs/utils/size_utils.cpp
+++ b/host/libs/config/config_fragment.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,16 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#pragma once
 
-#include "common/libs/utils/size_utils.h"
-
-#include <unistd.h>
+#include <json/json.h>
+#include <memory>
+#include <string>
 
 namespace cuttlefish {
 
-uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) {
-  uint64_t align = 1ULL << align_log;
-  return ((val + (align - 1)) / align) * align;
-}
+class ConfigFragment {
+ public:
+  virtual ~ConfigFragment();
+
+  virtual std::string Name() const = 0;
+  virtual Json::Value Serialize() const = 0;
+  virtual bool Deserialize(const Json::Value&) = 0;
+};
 
 }  // namespace cuttlefish
diff --git a/host/libs/config/custom_actions.cpp b/host/libs/config/custom_actions.cpp
index 1cc2e06..af5524e 100644
--- a/host/libs/config/custom_actions.cpp
+++ b/host/libs/config/custom_actions.cpp
@@ -16,12 +16,16 @@
 #include "host/libs/config/custom_actions.h"
 
 #include <android-base/logging.h>
+#include <android-base/strings.h>
 #include <json/json.h>
 
+#include <fstream>
 #include <optional>
 #include <string>
 #include <vector>
 
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/flag_parser.h"
 #include "host/libs/config/cuttlefish_config.h"
 
 namespace cuttlefish {
@@ -38,42 +42,41 @@
 const char* kCustomActionButtonTitle = "title";
 const char* kCustomActionButtonIconName = "icon_name";
 
-} //namespace
-
-
-CustomActionConfig::CustomActionConfig(const Json::Value& dictionary) {
-  if (dictionary.isMember(kCustomActionShellCommand) +
-          dictionary.isMember(kCustomActionServer) +
-          dictionary.isMember(kCustomActionDeviceStates) !=
-      1) {
-    LOG(FATAL) << "Custom action must contain exactly one of shell_command, "
+std::optional<CustomActionConfig> CustomActionConfigFromJson(
+    const Json::Value& dictionary) {
+  bool has_shell_command = dictionary.isMember(kCustomActionShellCommand);
+  bool has_server = dictionary.isMember(kCustomActionServer);
+  bool has_device_states = dictionary.isMember(kCustomActionDeviceStates);
+  if (!!has_shell_command + !!has_server + !!has_device_states != 1) {
+    LOG(ERROR) << "Custom action must contain exactly one of shell_command, "
                << "server, or device_states";
-    return;
+    return {};
   }
-  if (dictionary.isMember(kCustomActionShellCommand)) {
+  CustomActionConfig config;
+  if (has_shell_command) {
     // Shell command with one button.
     Json::Value button_entry = dictionary[kCustomActionButton];
-    buttons = {{button_entry[kCustomActionButtonCommand].asString(),
-                button_entry[kCustomActionButtonTitle].asString(),
-                button_entry[kCustomActionButtonIconName].asString()}};
-    shell_command = dictionary[kCustomActionShellCommand].asString();
-  } else if (dictionary.isMember(kCustomActionServer)) {
+    config.buttons = {{button_entry[kCustomActionButtonCommand].asString(),
+                       button_entry[kCustomActionButtonTitle].asString(),
+                       button_entry[kCustomActionButtonIconName].asString()}};
+    config.shell_command = dictionary[kCustomActionShellCommand].asString();
+  } else if (has_server) {
     // Action server with possibly multiple buttons.
     for (const Json::Value& button_entry : dictionary[kCustomActionButtons]) {
       ControlPanelButton button = {
           button_entry[kCustomActionButtonCommand].asString(),
           button_entry[kCustomActionButtonTitle].asString(),
           button_entry[kCustomActionButtonIconName].asString()};
-      buttons.push_back(button);
+      config.buttons.push_back(button);
     }
-    server = dictionary[kCustomActionServer].asString();
-  } else if (dictionary.isMember(kCustomActionDeviceStates)) {
+    config.server = dictionary[kCustomActionServer].asString();
+  } else if (has_device_states) {
     // Device state(s) with one button.
     // Each button press cycles to the next state, then repeats to the first.
     Json::Value button_entry = dictionary[kCustomActionButton];
-    buttons = {{button_entry[kCustomActionButtonCommand].asString(),
-                button_entry[kCustomActionButtonTitle].asString(),
-                button_entry[kCustomActionButtonIconName].asString()}};
+    config.buttons = {{button_entry[kCustomActionButtonCommand].asString(),
+                       button_entry[kCustomActionButtonTitle].asString(),
+                       button_entry[kCustomActionButtonIconName].asString()}};
     for (const Json::Value& device_state_entry :
          dictionary[kCustomActionDeviceStates]) {
       DeviceState state;
@@ -86,40 +89,42 @@
         state.hinge_angle_value =
             device_state_entry[kCustomActionDeviceStateHingeAngleValue].asInt();
       }
-      device_states.push_back(state);
+      config.device_states.push_back(state);
     }
   } else {
-    LOG(FATAL) << "Unknown custom action type.";
+    LOG(ERROR) << "Unknown custom action type.";
+    return {};
   }
+  return config;
 }
 
-Json::Value CustomActionConfig::ToJson() const {
-  Json::Value custom_action;
-  if (shell_command) {
+Json::Value ToJson(const CustomActionConfig& custom_action) {
+  Json::Value json;
+  if (custom_action.shell_command) {
     // Shell command with one button.
-    custom_action[kCustomActionShellCommand] = *shell_command;
-    custom_action[kCustomActionButton] = Json::Value();
-    custom_action[kCustomActionButton][kCustomActionButtonCommand] =
-        buttons[0].command;
-    custom_action[kCustomActionButton][kCustomActionButtonTitle] =
-        buttons[0].title;
-    custom_action[kCustomActionButton][kCustomActionButtonIconName] =
-        buttons[0].icon_name;
-  } else if (server) {
+    json[kCustomActionShellCommand] = *custom_action.shell_command;
+    json[kCustomActionButton] = Json::Value();
+    json[kCustomActionButton][kCustomActionButtonCommand] =
+        custom_action.buttons[0].command;
+    json[kCustomActionButton][kCustomActionButtonTitle] =
+        custom_action.buttons[0].title;
+    json[kCustomActionButton][kCustomActionButtonIconName] =
+        custom_action.buttons[0].icon_name;
+  } else if (custom_action.server) {
     // Action server with possibly multiple buttons.
-    custom_action[kCustomActionServer] = *server;
-    custom_action[kCustomActionButtons] = Json::Value(Json::arrayValue);
-    for (const auto& button : buttons) {
+    json[kCustomActionServer] = *custom_action.server;
+    json[kCustomActionButtons] = Json::Value(Json::arrayValue);
+    for (const auto& button : custom_action.buttons) {
       Json::Value button_entry;
       button_entry[kCustomActionButtonCommand] = button.command;
       button_entry[kCustomActionButtonTitle] = button.title;
       button_entry[kCustomActionButtonIconName] = button.icon_name;
-      custom_action[kCustomActionButtons].append(button_entry);
+      json[kCustomActionButtons].append(button_entry);
     }
-  } else if (!device_states.empty()) {
+  } else if (!custom_action.device_states.empty()) {
     // Device state(s) with one button.
-    custom_action[kCustomActionDeviceStates] = Json::Value(Json::arrayValue);
-    for (const auto& device_state : device_states) {
+    json[kCustomActionDeviceStates] = Json::Value(Json::arrayValue);
+    for (const auto& device_state : custom_action.device_states) {
       Json::Value device_state_entry;
       if (device_state.lid_switch_open) {
         device_state_entry[kCustomActionDeviceStateLidSwitchOpen] =
@@ -129,19 +134,167 @@
         device_state_entry[kCustomActionDeviceStateHingeAngleValue] =
             *device_state.hinge_angle_value;
       }
-      custom_action[kCustomActionDeviceStates].append(device_state_entry);
+      json[kCustomActionDeviceStates].append(device_state_entry);
     }
-    custom_action[kCustomActionButton] = Json::Value();
-    custom_action[kCustomActionButton][kCustomActionButtonCommand] =
-        buttons[0].command;
-    custom_action[kCustomActionButton][kCustomActionButtonTitle] =
-        buttons[0].title;
-    custom_action[kCustomActionButton][kCustomActionButtonIconName] =
-        buttons[0].icon_name;
+    json[kCustomActionButton] = Json::Value();
+    json[kCustomActionButton][kCustomActionButtonCommand] =
+        custom_action.buttons[0].command;
+    json[kCustomActionButton][kCustomActionButtonTitle] =
+        custom_action.buttons[0].title;
+    json[kCustomActionButton][kCustomActionButtonIconName] =
+        custom_action.buttons[0].icon_name;
   } else {
     LOG(FATAL) << "Unknown custom action type.";
   }
-  return custom_action;
+  return json;
+}
+
+std::string DefaultCustomActionConfig() {
+  auto custom_action_config_dir =
+      DefaultHostArtifactsPath("etc/cvd_custom_action_config");
+  if (DirectoryExists(custom_action_config_dir)) {
+    auto custom_action_configs = DirectoryContents(custom_action_config_dir);
+    // Two entries are always . and ..
+    if (custom_action_configs.size() > 3) {
+      LOG(ERROR) << "Expected at most one custom action config in "
+                 << custom_action_config_dir << ". Please delete extras.";
+    } else if (custom_action_configs.size() == 3) {
+      for (const auto& config : custom_action_configs) {
+        if (android::base::EndsWithIgnoreCase(config, ".json")) {
+          return custom_action_config_dir + "/" + config;
+        }
+      }
+    }
+  }
+  return "";
+}
+
+class CustomActionConfigImpl : public CustomActionConfigProvider {
+ public:
+  INJECT(CustomActionConfigImpl(ConfigFlag& config)) : config_(config) {
+    custom_action_config_flag_ = GflagsCompatFlag("custom_action_config");
+    custom_action_config_flag_.Help(
+        "Path to a custom action config JSON. Defaults to the file provided by "
+        "build variable CVD_CUSTOM_ACTION_CONFIG. If this build variable is "
+        "empty then the custom action config will be empty as well.");
+    custom_action_config_flag_.Getter(
+        [this]() { return custom_action_config_; });
+    custom_action_config_flag_.Setter([this](const FlagMatch& match) {
+      if (!match.value.empty() && !FileExists(match.value)) {
+        LOG(ERROR) << "custom_action_config file \"" << match.value << "\" "
+                   << "does not exist.";
+        return false;
+      }
+      custom_action_config_ = match.value;
+      return true;
+    });
+    // TODO(schuffelen): Access ConfigFlag directly for these values.
+    custom_actions_flag_ = GflagsCompatFlag("custom_actions");
+    custom_actions_flag_.Help(
+        "Serialized JSON of an array of custom action objects (in the same "
+        "format as custom action config JSON files). For use within --config "
+        "preset config files; prefer --custom_action_config to specify a "
+        "custom config file on the command line. Actions in this flag are "
+        "combined with actions in --custom_action_config.");
+    custom_actions_flag_.Setter([this](const FlagMatch& match) {
+      // Load the custom action from the --config preset file.
+      Json::CharReaderBuilder builder;
+      std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+      std::string errorMessage;
+      Json::Value custom_action_array(Json::arrayValue);
+      if (!reader->parse(&*match.value.begin(), &*match.value.end(),
+                         &custom_action_array, &errorMessage)) {
+        LOG(ERROR) << "Could not read custom actions config flag: "
+                   << errorMessage;
+        return false;
+      }
+      return AddJsonCustomActionConfigs(custom_action_array);
+    });
+  }
+
+  const std::vector<CustomActionConfig>& CustomActions() const override {
+    return custom_actions_;
+  }
+
+  // ConfigFragment
+  Json::Value Serialize() const override {
+    Json::Value actions_array(Json::arrayValue);
+    for (const auto& action : CustomActions()) {
+      actions_array.append(ToJson(action));
+    }
+    return actions_array;
+  }
+  bool Deserialize(const Json::Value& custom_actions_json) override {
+    return AddJsonCustomActionConfigs(custom_actions_json);
+  }
+
+  // FlagFeature
+  std::string Name() const override { return "CustomActionConfig"; }
+  std::unordered_set<FlagFeature*> Dependencies() const override {
+    return {static_cast<FlagFeature*>(&config_)};
+  }
+
+  bool Process(std::vector<std::string>& args) override {
+    custom_action_config_ = DefaultCustomActionConfig();
+    if (!ParseFlags(Flags(), args)) {
+      return false;
+    }
+    if (custom_action_config_ != "") {
+      Json::CharReaderBuilder builder;
+      std::ifstream ifs(custom_action_config_);
+      std::string errorMessage;
+      Json::Value custom_action_array(Json::arrayValue);
+      if (!Json::parseFromStream(builder, ifs, &custom_action_array,
+                                 &errorMessage)) {
+        LOG(ERROR) << "Could not read custom actions config file "
+                   << custom_action_config_ << ": " << errorMessage;
+        return false;
+      }
+      return AddJsonCustomActionConfigs(custom_action_array);
+    }
+    return true;
+  }
+  bool WriteGflagsCompatHelpXml(std::ostream& out) const override {
+    return WriteGflagsCompatXml(Flags(), out);
+  }
+
+ private:
+  std::vector<Flag> Flags() const {
+    return {custom_action_config_flag_, custom_actions_flag_};
+  }
+
+  bool AddJsonCustomActionConfigs(const Json::Value& custom_action_array) {
+    if (custom_action_array.type() != Json::arrayValue) {
+      LOG(ERROR) << "Expected a JSON array of custom actions";
+      return false;
+    }
+    for (const auto& custom_action_json : custom_action_array) {
+      auto custom_action = CustomActionConfigFromJson(custom_action_json);
+      if (custom_action) {
+        custom_actions_.push_back(*custom_action);
+      } else {
+        LOG(ERROR) << "Validation failed on a custom action";
+        return false;
+      }
+    }
+    return true;
+  }
+
+  ConfigFlag& config_;
+  Flag custom_action_config_flag_;
+  std::string custom_action_config_;
+  Flag custom_actions_flag_;
+  std::vector<CustomActionConfig> custom_actions_;
+};
+
+}  // namespace
+
+fruit::Component<fruit::Required<ConfigFlag>, CustomActionConfigProvider>
+CustomActionsComponent() {
+  return fruit::createComponent()
+      .bind<CustomActionConfigProvider, CustomActionConfigImpl>()
+      .addMultibinding<ConfigFragment, CustomActionConfigProvider>()
+      .addMultibinding<FlagFeature, CustomActionConfigProvider>();
 }
 
 }  // namespace cuttlefish
diff --git a/host/libs/config/custom_actions.h b/host/libs/config/custom_actions.h
index 6279401..73f3901 100644
--- a/host/libs/config/custom_actions.h
+++ b/host/libs/config/custom_actions.h
@@ -15,12 +15,15 @@
  */
 #pragma once
 
-#include <json/json.h>
-
+#include <fruit/fruit.h>
 #include <optional>
 #include <string>
 #include <vector>
 
+#include "host/libs/config/config_flag.h"
+#include "host/libs/config/config_fragment.h"
+#include "host/libs/config/feature.h"
+
 namespace cuttlefish {
 
 struct ControlPanelButton {
@@ -35,13 +38,18 @@
 };
 
 struct CustomActionConfig {
-  CustomActionConfig(const Json::Value&);
-  Json::Value ToJson() const;
-
   std::vector<ControlPanelButton> buttons;
   std::optional<std::string> shell_command;
   std::optional<std::string> server;
   std::vector<DeviceState> device_states;
 };
 
+class CustomActionConfigProvider : public FlagFeature, public ConfigFragment {
+ public:
+  virtual const std::vector<CustomActionConfig>& CustomActions() const = 0;
+};
+
+fruit::Component<fruit::Required<ConfigFlag>, CustomActionConfigProvider>
+CustomActionsComponent();
+
 }  // namespace cuttlefish
diff --git a/host/libs/config/cuttlefish_config.cpp b/host/libs/config/cuttlefish_config.cpp
index b5160d0..4b169af 100644
--- a/host/libs/config/cuttlefish_config.cpp
+++ b/host/libs/config/cuttlefish_config.cpp
@@ -35,14 +35,32 @@
 #include "common/libs/utils/environment.h"
 #include "common/libs/utils/files.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
+#include "host/libs/vm_manager/gem5_manager.h"
 #include "host/libs/vm_manager/qemu_manager.h"
 
 namespace cuttlefish {
 namespace {
 
+static constexpr int kDefaultInstance = 1;
+
+int InstanceFromString(std::string instance_str) {
+  if (android::base::StartsWith(instance_str, kVsocUserPrefix)) {
+    instance_str = instance_str.substr(std::string(kVsocUserPrefix).size());
+  } else if (android::base::StartsWith(instance_str, kCvdNamePrefix)) {
+    instance_str = instance_str.substr(std::string(kCvdNamePrefix).size());
+  }
+
+  int instance = std::stoi(instance_str);
+  if (instance <= 0) {
+    LOG(INFO) << "Failed to interpret \"" << instance_str << "\" as an id, "
+              << "using instance id " << kDefaultInstance;
+    return kDefaultInstance;
+  }
+  return instance;
+}
+
 int InstanceFromEnvironment() {
   static constexpr char kInstanceEnvironmentVariable[] = "CUTTLEFISH_INSTANCE";
-  static constexpr int kDefaultInstance = 1;
 
   // CUTTLEFISH_INSTANCE environment variable
   std::string instance_str = StringFromEnv(kInstanceEnvironmentVariable, "");
@@ -60,15 +78,8 @@
       LOG(DEBUG) << "Non-vsoc user, using instance id " << kDefaultInstance;
       return kDefaultInstance;
     }
-    instance_str = instance_str.substr(std::string(kVsocUserPrefix).size());
   }
-  int instance = std::stoi(instance_str);
-  if (instance <= 0) {
-    LOG(INFO) << "Failed to interpret \"" << instance_str << "\" as an id, "
-              << "using instance id " << kDefaultInstance;
-    return kDefaultInstance;
-  }
-  return instance;
+  return InstanceFromString(instance_str);
 }
 
 const char* kInstances = "instances";
@@ -81,18 +92,47 @@
 const char* const kGpuModeDrmVirgl = "drm_virgl";
 const char* const kGpuModeGfxStream = "gfxstream";
 
+const char* const kHwComposerAuto = "auto";
+const char* const kHwComposerDrm = "drm";
+const char* const kHwComposerRanchu = "ranchu";
+
 std::string DefaultEnvironmentPath(const char* environment_key,
                                    const char* default_value,
                                    const char* subpath) {
   return StringFromEnv(environment_key, default_value) + "/" + subpath;
 }
 
-static constexpr char kAssemblyDir[] = "assembly_dir";
-std::string CuttlefishConfig::assembly_dir() const {
-  return (*dictionary_)[kAssemblyDir].asString();
+ConfigFragment::~ConfigFragment() = default;
+
+static constexpr char kFragments[] = "fragments";
+bool CuttlefishConfig::LoadFragment(ConfigFragment& fragment) const {
+  if (!dictionary_->isMember(kFragments)) {
+    LOG(ERROR) << "Fragments member was missing";
+    return false;
+  }
+  const Json::Value& json_fragments = (*dictionary_)[kFragments];
+  if (!json_fragments.isMember(fragment.Name())) {
+    LOG(ERROR) << "Could not find a fragment called " << fragment.Name();
+    return false;
+  }
+  return fragment.Deserialize(json_fragments[fragment.Name()]);
 }
-void CuttlefishConfig::set_assembly_dir(const std::string& assembly_dir) {
-  (*dictionary_)[kAssemblyDir] = assembly_dir;
+bool CuttlefishConfig::SaveFragment(const ConfigFragment& fragment) {
+  Json::Value& json_fragments = (*dictionary_)[kFragments];
+  if (json_fragments.isMember(fragment.Name())) {
+    LOG(ERROR) << "Already have a fragment called " << fragment.Name();
+    return false;
+  }
+  json_fragments[fragment.Name()] = fragment.Serialize();
+  return true;
+}
+
+static constexpr char kRootDir[] = "root_dir";
+std::string CuttlefishConfig::root_dir() const {
+  return (*dictionary_)[kRootDir].asString();
+}
+void CuttlefishConfig::set_root_dir(const std::string& root_dir) {
+  (*dictionary_)[kRootDir] = root_dir;
 }
 
 static constexpr char kVmManager[] = "vm_manager";
@@ -111,6 +151,38 @@
   (*dictionary_)[kGpuMode] = name;
 }
 
+static constexpr char kGpuCaptureBinary[] = "gpu_capture_binary";
+std::string CuttlefishConfig::gpu_capture_binary() const {
+  return (*dictionary_)[kGpuCaptureBinary].asString();
+}
+void CuttlefishConfig::set_gpu_capture_binary(const std::string& name) {
+  (*dictionary_)[kGpuCaptureBinary] = name;
+}
+
+static constexpr char kHWComposer[] = "hwcomposer";
+std::string CuttlefishConfig::hwcomposer() const {
+  return (*dictionary_)[kHWComposer].asString();
+}
+void CuttlefishConfig::set_hwcomposer(const std::string& name) {
+  (*dictionary_)[kHWComposer] = name;
+}
+
+static constexpr char kEnableGpuUdmabuf[] = "enable_gpu_udmabuf";
+void CuttlefishConfig::set_enable_gpu_udmabuf(const bool enable_gpu_udmabuf) {
+  (*dictionary_)[kEnableGpuUdmabuf] = enable_gpu_udmabuf;
+}
+bool CuttlefishConfig::enable_gpu_udmabuf() const {
+  return (*dictionary_)[kEnableGpuUdmabuf].asBool();
+}
+
+static constexpr char kEnableGpuAngle[] = "enable_gpu_angle";
+void CuttlefishConfig::set_enable_gpu_angle(const bool enable_gpu_angle) {
+  (*dictionary_)[kEnableGpuAngle] = enable_gpu_angle;
+}
+bool CuttlefishConfig::enable_gpu_angle() const {
+  return (*dictionary_)[kEnableGpuAngle].asBool();
+}
+
 static constexpr char kCpus[] = "cpus";
 int CuttlefishConfig::cpus() const { return (*dictionary_)[kCpus].asInt(); }
 void CuttlefishConfig::set_cpus(int cpus) { (*dictionary_)[kCpus] = cpus; }
@@ -190,35 +262,6 @@
   return (*dictionary_)[kCuttlefishEnvPath].asString();
 }
 
-static AdbMode stringToAdbMode(std::string mode) {
-  std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower);
-  if (mode == "vsock_tunnel") {
-    return AdbMode::VsockTunnel;
-  } else if (mode == "vsock_half_tunnel") {
-    return AdbMode::VsockHalfTunnel;
-  } else if (mode == "native_vsock") {
-    return AdbMode::NativeVsock;
-  } else {
-    return AdbMode::Unknown;
-  }
-}
-
-static constexpr char kAdbMode[] = "adb_mode";
-std::set<AdbMode> CuttlefishConfig::adb_mode() const {
-  std::set<AdbMode> args_set;
-  for (auto& mode : (*dictionary_)[kAdbMode]) {
-    args_set.insert(stringToAdbMode(mode.asString()));
-  }
-  return args_set;
-}
-void CuttlefishConfig::set_adb_mode(const std::set<std::string>& mode) {
-  Json::Value mode_json_obj(Json::arrayValue);
-  for (const auto& arg : mode) {
-    mode_json_obj.append(arg);
-  }
-  (*dictionary_)[kAdbMode] = mode_json_obj;
-}
-
 static SecureHal StringToSecureHal(std::string mode) {
   std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower);
   if (mode == "keymint") {
@@ -270,12 +313,12 @@
   (*dictionary_)[kCrosvmBinary] = crosvm_binary;
 }
 
-static constexpr char kTpmDevice[] = "tpm_device";
-std::string CuttlefishConfig::tpm_device() const {
-  return (*dictionary_)[kTpmDevice].asString();
+static constexpr char kGem5BinaryDir[] = "gem5_binary_dir";
+std::string CuttlefishConfig::gem5_binary_dir() const {
+  return (*dictionary_)[kGem5BinaryDir].asString();
 }
-void CuttlefishConfig::set_tpm_device(const std::string& tpm_device) {
-  (*dictionary_)[kTpmDevice] = tpm_device;
+void CuttlefishConfig::set_gem5_binary_dir(const std::string& gem5_binary_dir) {
+  (*dictionary_)[kGem5BinaryDir] = gem5_binary_dir;
 }
 
 static constexpr char kEnableGnssGrpcProxy[] = "enable_gnss_grpc_proxy";
@@ -286,14 +329,6 @@
   return (*dictionary_)[kEnableGnssGrpcProxy].asBool();
 }
 
-static constexpr char kEnableVncServer[] = "enable_vnc_server";
-bool CuttlefishConfig::enable_vnc_server() const {
-  return (*dictionary_)[kEnableVncServer].asBool();
-}
-void CuttlefishConfig::set_enable_vnc_server(bool enable_vnc_server) {
-  (*dictionary_)[kEnableVncServer] = enable_vnc_server;
-}
-
 static constexpr char kEnableSandbox[] = "enable_sandbox";
 void CuttlefishConfig::set_enable_sandbox(const bool enable_sandbox) {
   (*dictionary_)[kEnableSandbox] = enable_sandbox;
@@ -330,30 +365,6 @@
   return (*dictionary_)[kEnableVehicleHalServer].asBool();
 }
 
-static constexpr char kVehicleHalServerBinary[] = "vehicle_hal_server_binary";
-void CuttlefishConfig::set_vehicle_hal_grpc_server_binary(const std::string& vehicle_hal_server_binary) {
-  (*dictionary_)[kVehicleHalServerBinary] = vehicle_hal_server_binary;
-}
-std::string CuttlefishConfig::vehicle_hal_grpc_server_binary() const {
-  return (*dictionary_)[kVehicleHalServerBinary].asString();
-}
-
-static constexpr char kCustomActions[] = "custom_actions";
-void CuttlefishConfig::set_custom_actions(const std::vector<CustomActionConfig>& actions) {
-  Json::Value actions_array(Json::arrayValue);
-  for (const auto& action : actions) {
-    actions_array.append(action.ToJson());
-  }
-  (*dictionary_)[kCustomActions] = actions_array;
-}
-std::vector<CustomActionConfig> CuttlefishConfig::custom_actions() const {
-  std::vector<CustomActionConfig> result;
-  for (Json::Value custom_action : (*dictionary_)[kCustomActions]) {
-    result.push_back(CustomActionConfig(custom_action));
-  }
-  return result;
-}
-
 static constexpr char kWebRTCAssetsDir[] = "webrtc_assets_dir";
 void CuttlefishConfig::set_webrtc_assets_dir(const std::string& webrtc_assets_dir) {
   (*dictionary_)[kWebRTCAssetsDir] = webrtc_assets_dir;
@@ -379,14 +390,6 @@
   (*dictionary_)[kRestartSubprocesses] = restart_subprocesses;
 }
 
-static constexpr char kRunAdbConnector[] = "run_adb_connector";
-bool CuttlefishConfig::run_adb_connector() const {
-  return (*dictionary_)[kRunAdbConnector].asBool();
-}
-void CuttlefishConfig::set_run_adb_connector(bool run_adb_connector) {
-  (*dictionary_)[kRunAdbConnector] = run_adb_connector;
-}
-
 static constexpr char kRunAsDaemon[] = "run_as_daemon";
 bool CuttlefishConfig::run_as_daemon() const {
   return (*dictionary_)[kRunAsDaemon].asBool();
@@ -411,14 +414,6 @@
   (*dictionary_)[kBlankDataImageMb] = blank_data_image_mb;
 }
 
-static constexpr char kBlankDataImageFmt[] = "blank_data_image_fmt";
-std::string CuttlefishConfig::blank_data_image_fmt() const {
-  return (*dictionary_)[kBlankDataImageFmt].asString();
-}
-void CuttlefishConfig::set_blank_data_image_fmt(const std::string& blank_data_image_fmt) {
-  (*dictionary_)[kBlankDataImageFmt] = blank_data_image_fmt;
-}
-
 static constexpr char kBootloader[] = "bootloader";
 std::string CuttlefishConfig::bootloader() const {
   return (*dictionary_)[kBootloader].asString();
@@ -498,6 +493,14 @@
   return (*dictionary_)[kSigServerPath].asString();
 }
 
+static constexpr char kSigServerSecure[] = "webrtc_sig_server_secure";
+void CuttlefishConfig::set_sig_server_secure(bool secure) {
+  (*dictionary_)[kSigServerSecure] = secure;
+}
+bool CuttlefishConfig::sig_server_secure() const {
+  return (*dictionary_)[kSigServerSecure].asBool();
+}
+
 static constexpr char kSigServerStrict[] = "webrtc_sig_server_strict";
 void CuttlefishConfig::set_sig_server_strict(bool strict) {
   (*dictionary_)[kSigServerStrict] = strict;
@@ -570,14 +573,6 @@
   return (*dictionary_)[kGuestEnforceSecurity].asBool();
 }
 
-const char* kGuestAuditSecurity = "guest_audit_security";
-void CuttlefishConfig::set_guest_audit_security(bool guest_audit_security) {
-  (*dictionary_)[kGuestAuditSecurity] = guest_audit_security;
-}
-bool CuttlefishConfig::guest_audit_security() const {
-  return (*dictionary_)[kGuestAuditSecurity].asBool();
-}
-
 static constexpr char kenableHostBluetooth[] = "enable_host_bluetooth";
 void CuttlefishConfig::set_enable_host_bluetooth(bool enable_host_bluetooth) {
   (*dictionary_)[kenableHostBluetooth] = enable_host_bluetooth;
@@ -615,7 +610,8 @@
 }
 
 static constexpr char kExtraKernelCmdline[] = "extra_kernel_cmdline";
-void CuttlefishConfig::set_extra_kernel_cmdline(std::string extra_cmdline) {
+void CuttlefishConfig::set_extra_kernel_cmdline(
+    const std::string& extra_cmdline) {
   Json::Value args_json_obj(Json::arrayValue);
   for (const auto& arg : android::base::Split(extra_cmdline, " ")) {
     args_json_obj.append(arg);
@@ -630,6 +626,23 @@
   return cmdline;
 }
 
+static constexpr char kExtraBootconfigArgs[] = "extra_bootconfig_args";
+void CuttlefishConfig::set_extra_bootconfig_args(
+    const std::string& extra_bootconfig_args) {
+  Json::Value args_json_obj(Json::arrayValue);
+  for (const auto& arg : android::base::Split(extra_bootconfig_args, " ")) {
+    args_json_obj.append(arg);
+  }
+  (*dictionary_)[kExtraBootconfigArgs] = args_json_obj;
+}
+std::vector<std::string> CuttlefishConfig::extra_bootconfig_args() const {
+  std::vector<std::string> bootconfig;
+  for (const Json::Value& arg : (*dictionary_)[kExtraBootconfigArgs]) {
+    bootconfig.push_back(arg.asString());
+  }
+  return bootconfig;
+}
+
 static constexpr char kRilDns[] = "ril_dns";
 void CuttlefishConfig::set_ril_dns(const std::string& ril_dns) {
   (*dictionary_)[kRilDns] = ril_dns;
@@ -664,7 +677,8 @@
 std::string CuttlefishConfig::console_dev() const {
   auto can_use_virtio_console = !kgdb() && !use_bootloader();
   std::string console_dev;
-  if (can_use_virtio_console) {
+  if (can_use_virtio_console ||
+      vm_manager() == vm_manager::Gem5Manager::name()) {
     // If kgdb and the bootloader are disabled, the Android serial console
     // spawns on a virtio-console port. If the bootloader is enabled, virtio
     // console can't be used since uboot doesn't support it.
@@ -690,6 +704,91 @@
   return (*dictionary_)[kVhostNet].asBool();
 }
 
+static constexpr char kVhostUserMac80211Hwsim[] = "vhost_user_mac80211_hwsim";
+void CuttlefishConfig::set_vhost_user_mac80211_hwsim(const std::string& path) {
+  (*dictionary_)[kVhostUserMac80211Hwsim] = path;
+}
+std::string CuttlefishConfig::vhost_user_mac80211_hwsim() const {
+  return (*dictionary_)[kVhostUserMac80211Hwsim].asString();
+}
+
+static constexpr char kWmediumdApiServerSocket[] = "wmediumd_api_server_socket";
+void CuttlefishConfig::set_wmediumd_api_server_socket(const std::string& path) {
+  (*dictionary_)[kWmediumdApiServerSocket] = path;
+}
+std::string CuttlefishConfig::wmediumd_api_server_socket() const {
+  return (*dictionary_)[kWmediumdApiServerSocket].asString();
+}
+
+static constexpr char kApRootfsImage[] = "ap_rootfs_image";
+std::string CuttlefishConfig::ap_rootfs_image() const {
+  return (*dictionary_)[kApRootfsImage].asString();
+}
+void CuttlefishConfig::set_ap_rootfs_image(const std::string& ap_rootfs_image) {
+  (*dictionary_)[kApRootfsImage] = ap_rootfs_image;
+}
+
+static constexpr char kApKernelImage[] = "ap_kernel_image";
+std::string CuttlefishConfig::ap_kernel_image() const {
+  return (*dictionary_)[kApKernelImage].asString();
+}
+void CuttlefishConfig::set_ap_kernel_image(const std::string& ap_kernel_image) {
+  (*dictionary_)[kApKernelImage] = ap_kernel_image;
+}
+
+static constexpr char kWmediumdConfig[] = "wmediumd_config";
+void CuttlefishConfig::set_wmediumd_config(const std::string& config) {
+  (*dictionary_)[kWmediumdConfig] = config;
+}
+std::string CuttlefishConfig::wmediumd_config() const {
+  return (*dictionary_)[kWmediumdConfig].asString();
+}
+
+static constexpr char kRootcanalHciPort[] = "rootcanal_hci_port";
+int CuttlefishConfig::rootcanal_hci_port() const {
+  return (*dictionary_)[kRootcanalHciPort].asInt();
+}
+void CuttlefishConfig::set_rootcanal_hci_port(int rootcanal_hci_port) {
+  (*dictionary_)[kRootcanalHciPort] = rootcanal_hci_port;
+}
+
+static constexpr char kRootcanalLinkPort[] = "rootcanal_link_port";
+int CuttlefishConfig::rootcanal_link_port() const {
+  return (*dictionary_)[kRootcanalLinkPort].asInt();
+}
+void CuttlefishConfig::set_rootcanal_link_port(int rootcanal_link_port) {
+  (*dictionary_)[kRootcanalLinkPort] = rootcanal_link_port;
+}
+
+static constexpr char kRootcanalTestPort[] = "rootcanal_test_port";
+int CuttlefishConfig::rootcanal_test_port() const {
+  return (*dictionary_)[kRootcanalTestPort].asInt();
+}
+void CuttlefishConfig::set_rootcanal_test_port(int rootcanal_test_port) {
+  (*dictionary_)[kRootcanalTestPort] = rootcanal_test_port;
+}
+
+static constexpr char kRootcanalConfigFile[] = "rootcanal_config_file";
+std::string CuttlefishConfig::rootcanal_config_file() const {
+  return (*dictionary_)[kRootcanalConfigFile].asString();
+}
+void CuttlefishConfig::set_rootcanal_config_file(
+    const std::string& rootcanal_config_file) {
+  (*dictionary_)[kRootcanalConfigFile] =
+      DefaultHostArtifactsPath(rootcanal_config_file);
+}
+
+static constexpr char kRootcanalDefaultCommandsFile[] =
+    "rootcanal_default_commands_file";
+std::string CuttlefishConfig::rootcanal_default_commands_file() const {
+  return (*dictionary_)[kRootcanalDefaultCommandsFile].asString();
+}
+void CuttlefishConfig::set_rootcanal_default_commands_file(
+    const std::string& rootcanal_default_commands_file) {
+  (*dictionary_)[kRootcanalDefaultCommandsFile] =
+      DefaultHostArtifactsPath(rootcanal_default_commands_file);
+}
+
 static constexpr char kRecordScreen[] = "record_screen";
 void CuttlefishConfig::set_record_screen(bool record_screen) {
   (*dictionary_)[kRecordScreen] = record_screen;
@@ -738,15 +837,29 @@
   (*dictionary_)[kBootconfigSupported] = bootconfig_supported;
 }
 
-// Creates the (initially empty) config object and populates it with values from
-// the config file if the CUTTLEFISH_CONFIG_FILE env variable is present.
-// Returns nullptr if there was an error loading from file
-/*static*/ CuttlefishConfig* CuttlefishConfig::BuildConfigImpl() {
-  auto config_file_path = StringFromEnv(kCuttlefishConfigEnvVarName,
-                                        GetGlobalConfigFileLink());
+static constexpr char kUserdataFormat[] = "userdata_format";
+std::string CuttlefishConfig::userdata_format() const {
+  return (*dictionary_)[kUserdataFormat].asString();
+}
+void CuttlefishConfig::set_userdata_format(const std::string& userdata_format) {
+  auto fmt = userdata_format;
+  std::transform(fmt.begin(), fmt.end(), fmt.begin(), ::tolower);
+  (*dictionary_)[kUserdataFormat] = fmt;
+}
+
+static constexpr char kApImageDevPath[] = "ap_image_dev_path";
+std::string CuttlefishConfig::ap_image_dev_path() const {
+  return (*dictionary_)[kApImageDevPath].asString();
+}
+void CuttlefishConfig::set_ap_image_dev_path(const std::string& dev_path) {
+  (*dictionary_)[kApImageDevPath] = dev_path;
+}
+
+/*static*/ CuttlefishConfig* CuttlefishConfig::BuildConfigImpl(
+    const std::string& path) {
   auto ret = new CuttlefishConfig();
   if (ret) {
-    auto loaded = ret->LoadFromFile(config_file_path.c_str());
+    auto loaded = ret->LoadFromFile(path.c_str());
     if (!loaded) {
       delete ret;
       return nullptr;
@@ -755,8 +868,19 @@
   return ret;
 }
 
+/*static*/ std::unique_ptr<const CuttlefishConfig>
+CuttlefishConfig::GetFromFile(const std::string& path) {
+  return std::unique_ptr<const CuttlefishConfig>(BuildConfigImpl(path));
+}
+
+// Creates the (initially empty) config object and populates it with values from
+// the config file if the CUTTLEFISH_CONFIG_FILE env variable is present.
+// Returns nullptr if there was an error loading from file
 /*static*/ const CuttlefishConfig* CuttlefishConfig::Get() {
-  static std::shared_ptr<CuttlefishConfig> config(BuildConfigImpl());
+  auto config_file_path =
+      StringFromEnv(kCuttlefishConfigEnvVarName, GetGlobalConfigFileLink());
+  static std::shared_ptr<CuttlefishConfig> config(
+      BuildConfigImpl(config_file_path));
   return config.get();
 }
 
@@ -800,11 +924,28 @@
   return !ofs.fail();
 }
 
+std::string CuttlefishConfig::instances_dir() const {
+  return AbsolutePath(root_dir() + "/instances");
+}
+
+std::string CuttlefishConfig::InstancesPath(
+    const std::string& file_name) const {
+  return AbsolutePath(instances_dir() + "/" + file_name);
+}
+
+std::string CuttlefishConfig::assembly_dir() const {
+  return AbsolutePath(root_dir() + "/assembly");
+}
+
 std::string CuttlefishConfig::AssemblyPath(
     const std::string& file_name) const {
   return AbsolutePath(assembly_dir() + "/" + file_name);
 }
 
+std::string CuttlefishConfig::os_composite_disk_path() const {
+  return AssemblyPath("os_composite.img");
+}
+
 CuttlefishConfig::MutableInstanceSpecific CuttlefishConfig::ForInstance(int num) {
   return MutableInstanceSpecific(this, std::to_string(num));
 }
@@ -813,8 +954,13 @@
   return InstanceSpecific(this, std::to_string(num));
 }
 
+CuttlefishConfig::InstanceSpecific CuttlefishConfig::ForInstanceName(
+    const std::string& name) const {
+  return ForInstance(InstanceFromString(name));
+}
+
 CuttlefishConfig::InstanceSpecific CuttlefishConfig::ForDefaultInstance() const {
-  return InstanceSpecific(this, std::to_string(GetInstance()));
+  return ForInstance(GetInstance());
 }
 
 std::vector<CuttlefishConfig::InstanceSpecific> CuttlefishConfig::Instances() const {
@@ -826,6 +972,39 @@
   return instances;
 }
 
+std::vector<std::string> CuttlefishConfig::instance_dirs() const {
+  std::vector<std::string> result;
+  for (const auto& instance : Instances()) {
+    result.push_back(instance.instance_dir());
+  }
+  return result;
+}
+
+static constexpr char kInstanceNames[] = "instance_names";
+void CuttlefishConfig::set_instance_names(
+    const std::vector<std::string>& instance_names) {
+  Json::Value args_json_obj(Json::arrayValue);
+  for (const auto& name : instance_names) {
+    args_json_obj.append(name);
+  }
+  (*dictionary_)[kInstanceNames] = args_json_obj;
+}
+std::vector<std::string> CuttlefishConfig::instance_names() const {
+  // NOTE: The structure of this field needs to remain stable, since
+  // cvd_server may call this on config JSON files from various builds.
+  //
+  // This info is duplicated into its own field here so it is simpler
+  // to keep stable, rather than parsing from Instances()::instance_name.
+  //
+  // Any non-stable changes must be accompanied by an uprev to the
+  // cvd_server major version.
+  std::vector<std::string> names;
+  for (const Json::Value& name : (*dictionary_)[kInstanceNames]) {
+    names.push_back(name.asString());
+  }
+  return names;
+}
+
 int GetInstance() {
   static int instance_id = InstanceFromEnvironment();
   return instance_id;
@@ -864,7 +1043,7 @@
 }
 
 std::string DefaultHostArtifactsPath(const std::string& file_name) {
-  return (StringFromEnv("ANDROID_SOONG_HOST_OUT", StringFromEnv("HOME", ".")) + "/") +
+  return (StringFromEnv("ANDROID_HOST_OUT", StringFromEnv("HOME", ".")) + "/") +
          file_name;
 }
 
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index db3207e..78861e0 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -26,7 +26,7 @@
 #include <vector>
 
 #include "common/libs/utils/environment.h"
-#include "host/libs/config/custom_actions.h"
+#include "host/libs/config/config_fragment.h"
 
 namespace Json {
 class Value;
@@ -35,13 +35,11 @@
 namespace cuttlefish {
 constexpr char kLogcatSerialMode[] = "serial";
 constexpr char kLogcatVsockMode[] = "vsock";
-}
-
-namespace cuttlefish {
 
 constexpr char kDefaultUuidPrefix[] = "699acfc4-c8c4-11e7-882b-5065f31dc1";
 constexpr char kCuttlefishConfigEnvVarName[] = "CUTTLEFISH_CONFIG_FILE";
 constexpr char kVsocUserPrefix[] = "vsoc-";
+constexpr char kCvdNamePrefix[] = "cvd-";
 constexpr char kBootStartedMessage[] ="VIRTUAL_DEVICE_BOOT_STARTED";
 constexpr char kBootCompletedMessage[] = "VIRTUAL_DEVICE_BOOT_COMPLETED";
 constexpr char kBootFailedMessage[] = "VIRTUAL_DEVICE_BOOT_FAILED";
@@ -56,14 +54,10 @@
     "VIRTUAL_DEVICE_DISPLAY_POWER_MODE_CHANGED";
 constexpr char kInternalDirName[] = "internal";
 constexpr char kSharedDirName[] = "shared";
+constexpr char kLogDirName[] = "logs";
 constexpr char kCrosvmVarEmptyDir[] = "/var/empty";
-
-enum class AdbMode {
-  VsockTunnel,
-  VsockHalfTunnel,
-  NativeVsock,
-  Unknown,
-};
+constexpr char kKernelLoadedMessage[] = "] Linux version";
+constexpr char kBootloaderLoadedMessage[] = "U-Boot 20";
 
 enum class SecureHal {
   Unknown,
@@ -75,6 +69,8 @@
 class CuttlefishConfig {
  public:
   static const CuttlefishConfig* Get();
+  static std::unique_ptr<const CuttlefishConfig> GetFromFile(
+      const std::string& path);
   static bool ConfigExists();
 
   CuttlefishConfig();
@@ -86,17 +82,38 @@
   // processes by passing the --config_file option.
   bool SaveToFile(const std::string& file) const;
 
-  std::string assembly_dir() const;
-  void set_assembly_dir(const std::string& assembly_dir);
+  bool SaveFragment(const ConfigFragment&);
+  bool LoadFragment(ConfigFragment&) const;
 
+  std::string root_dir() const;
+  void set_root_dir(const std::string& root_dir);
+
+  std::string instances_dir() const;
+  std::string InstancesPath(const std::string&) const;
+
+  std::string assembly_dir() const;
   std::string AssemblyPath(const std::string&) const;
 
+  std::string os_composite_disk_path() const;
+
   std::string vm_manager() const;
   void set_vm_manager(const std::string& name);
 
   std::string gpu_mode() const;
   void set_gpu_mode(const std::string& name);
 
+  std::string gpu_capture_binary() const;
+  void set_gpu_capture_binary(const std::string&);
+
+  std::string hwcomposer() const;
+  void set_hwcomposer(const std::string&);
+
+  void set_enable_gpu_udmabuf(const bool enable_gpu_udmabuf);
+  bool enable_gpu_udmabuf() const;
+
+  void set_enable_gpu_angle(const bool enable_gpu_angle);
+  bool enable_gpu_angle() const;
+
   int cpus() const;
   void set_cpus(int cpus);
 
@@ -122,9 +139,6 @@
   void set_cuttlefish_env_path(const std::string& path);
   std::string cuttlefish_env_path() const;
 
-  void set_adb_mode(const std::set<std::string>& modes);
-  std::set<AdbMode> adb_mode() const;
-
   void set_secure_hals(const std::set<std::string>& hals);
   std::set<SecureHal> secure_hals() const;
 
@@ -137,11 +151,8 @@
   void set_crosvm_binary(const std::string& crosvm_binary);
   std::string crosvm_binary() const;
 
-  void set_tpm_device(const std::string& tpm_device);
-  std::string tpm_device() const;
-
-  void set_enable_vnc_server(bool enable_vnc_server);
-  bool enable_vnc_server() const;
+  void set_gem5_binary_dir(const std::string& gem5_binary_dir);
+  std::string gem5_binary_dir() const;
 
   void set_enable_sandbox(const bool enable_sandbox);
   bool enable_sandbox() const;
@@ -161,18 +172,9 @@
   void set_enable_vehicle_hal_grpc_server(bool enable_vhal_server);
   bool enable_vehicle_hal_grpc_server() const;
 
-  void set_vehicle_hal_grpc_server_binary(const std::string& vhal_server_binary);
-  std::string vehicle_hal_grpc_server_binary() const;
-
-  void set_custom_actions(const std::vector<CustomActionConfig>& actions);
-  std::vector<CustomActionConfig> custom_actions() const;
-
   void set_restart_subprocesses(bool restart_subprocesses);
   bool restart_subprocesses() const;
 
-  void set_run_adb_connector(bool run_adb_connector);
-  bool run_adb_connector() const;
-
   void set_enable_gnss_grpc_proxy(const bool enable_gnss_grpc_proxy);
   bool enable_gnss_grpc_proxy() const;
 
@@ -185,9 +187,6 @@
   void set_blank_data_image_mb(int blank_data_image_mb);
   int blank_data_image_mb() const;
 
-  void set_blank_data_image_fmt(const std::string& blank_data_image_fmt);
-  std::string blank_data_image_fmt() const;
-
   void set_bootloader(const std::string& bootloader_path);
   std::string bootloader() const;
 
@@ -203,9 +202,6 @@
   void set_guest_enforce_security(bool guest_enforce_security);
   bool guest_enforce_security() const;
 
-  void set_guest_audit_security(bool guest_audit_security);
-  bool guest_audit_security() const;
-
   void set_enable_host_bluetooth(bool enable_host_bluetooth);
   bool enable_host_bluetooth() const;
 
@@ -221,9 +217,12 @@
   void set_metrics_binary(const std::string& metrics_binary);
   std::string metrics_binary() const;
 
-  void set_extra_kernel_cmdline(std::string extra_cmdline);
+  void set_extra_kernel_cmdline(const std::string& extra_cmdline);
   std::vector<std::string> extra_kernel_cmdline() const;
 
+  void set_extra_bootconfig_args(const std::string& extra_bootconfig_args);
+  std::vector<std::string> extra_bootconfig_args() const;
+
   // A directory containing the SSL certificates for the signaling server
   void set_webrtc_certs_dir(const std::string& certs_dir);
   std::string webrtc_certs_dir() const;
@@ -250,6 +249,11 @@
   void set_sig_server_path(const std::string& path);
   std::string sig_server_path() const;
 
+  // Whether the webrtc process should use a secure connection (WSS) to the
+  // signaling server.
+  void set_sig_server_secure(bool secure);
+  bool sig_server_secure() const;
+
   // Whether the webrtc process should attempt to verify the authenticity of the
   // signaling server (reject self signed certificates)
   void set_sig_server_strict(bool strict);
@@ -292,6 +296,37 @@
   void set_vhost_net(bool vhost_net);
   bool vhost_net() const;
 
+  void set_vhost_user_mac80211_hwsim(const std::string& path);
+  std::string vhost_user_mac80211_hwsim() const;
+
+  void set_wmediumd_api_server_socket(const std::string& path);
+  std::string wmediumd_api_server_socket() const;
+
+  void set_ap_rootfs_image(const std::string& path);
+  std::string ap_rootfs_image() const;
+
+  void set_ap_kernel_image(const std::string& path);
+  std::string ap_kernel_image() const;
+
+  void set_wmediumd_config(const std::string& path);
+  std::string wmediumd_config() const;
+
+  void set_rootcanal_hci_port(int rootcanal_hci_port);
+  int rootcanal_hci_port() const;
+
+  void set_rootcanal_link_port(int rootcanal_link_port);
+  int rootcanal_link_port() const;
+
+  void set_rootcanal_test_port(int rootcanal_test_port);
+  int rootcanal_test_port() const;
+
+  void set_rootcanal_config_file(const std::string& rootcanal_config_file);
+  std::string rootcanal_config_file() const;
+
+  void set_rootcanal_default_commands_file(
+      const std::string& rootcanal_default_commands_file);
+  std::string rootcanal_default_commands_file() const;
+
   void set_record_screen(bool record_screen);
   bool record_screen() const;
 
@@ -310,21 +345,32 @@
   void set_bootconfig_supported(bool bootconfig_supported);
   bool bootconfig_supported() const;
 
+  void set_userdata_format(const std::string& userdata_format);
+  std::string userdata_format() const;
+
+  // The path of an AP image in composite disk
+  std::string ap_image_dev_path() const;
+  void set_ap_image_dev_path(const std::string& dev_path);
+
   class InstanceSpecific;
   class MutableInstanceSpecific;
 
   MutableInstanceSpecific ForInstance(int instance_num);
   InstanceSpecific ForInstance(int instance_num) const;
+  InstanceSpecific ForInstanceName(const std::string& name) const;
   InstanceSpecific ForDefaultInstance() const;
 
   std::vector<InstanceSpecific> Instances() const;
+  std::vector<std::string> instance_dirs() const;
+
+  void set_instance_names(const std::vector<std::string>& instance_names);
+  std::vector<std::string> instance_names() const;
 
   // A view into an existing CuttlefishConfig object for a particular instance.
   class InstanceSpecific {
     const CuttlefishConfig* config_;
     std::string id_;
     friend InstanceSpecific CuttlefishConfig::ForInstance(int num) const;
-    friend InstanceSpecific CuttlefishConfig::ForDefaultInstance() const;
     friend std::vector<InstanceSpecific> CuttlefishConfig::Instances() const;
 
     InstanceSpecific(const CuttlefishConfig* config, const std::string& id)
@@ -337,8 +383,8 @@
     // If any of the following port numbers is 0, the relevant service is not
     // running on the guest.
 
-    // Port number to connect to vnc server on the host
-    int vnc_server_port() const;
+    // Port number for qemu to run a vnc server on the host
+    int qemu_vnc_server_port() const;
     // Port number to connect to the tombstone receiver on the host
     int tombstone_receiver_port() const;
     // Port number to connect to the config server on the host
@@ -349,29 +395,21 @@
     // Port number to connect to the touch server on the host. (Only
     // operational if QEMU is the vmm.)
     int touch_server_port() const;
-    // Port number to connect to the frame server on the host. (Only
-    // operational if using swiftshader as the GPU.)
-    int frames_server_port() const;
     // Port number to connect to the vehicle HAL server on the host
     int vehicle_hal_server_port() const;
     // Port number to connect to the audiocontrol server on the guest
     int audiocontrol_server_port() const;
     // Port number to connect to the adb server on the host
-    int host_port() const;
+    int adb_host_port() const;
+    // Device-specific ID to distinguish modem simulators. Must be 4 digits.
+    int modem_simulator_host_id() const;
     // Port number to connect to the gnss grpc proxy server on the host
     int gnss_grpc_proxy_server_port() const;
     std::string adb_ip_and_port() const;
-    // Port number to connect to the root-canal on the host
-    int rootcanal_hci_port() const;
-    int rootcanal_link_port() const;
-    int rootcanal_test_port() const;
     // Port number to connect to the camera hal on the guest
     int camera_server_port() const;
-    std::string rootcanal_config_file() const;
-    std::string rootcanal_default_commands_file() const;
 
     std::string adb_device_name() const;
-    std::string device_title() const;
     std::string gnss_file_path() const;
     std::string mobile_bridge_name() const;
     std::string mobile_tap_name() const;
@@ -388,6 +426,7 @@
     // directory..
     std::string PerInstancePath(const char* file_name) const;
     std::string PerInstanceInternalPath(const char* file_name) const;
+    std::string PerInstanceLogPath(const std::string& file_name) const;
 
     std::string instance_dir() const;
 
@@ -398,11 +437,12 @@
     std::string switches_socket_path() const;
     std::string frames_socket_path() const;
 
-    // mock hal guest socket that will be vsock/virtio later on
-    std::string confui_hal_guest_socket_path() const;
+    int confui_host_vsock_port() const;
 
     std::string access_kregistry_path() const;
 
+    std::string hwcomposer_pmem_path() const;
+
     std::string pstore_path() const;
 
     std::string console_path() const;
@@ -427,14 +467,10 @@
 
     std::string sdcard_path() const;
 
-    std::string os_composite_disk_path() const;
-
     std::string persistent_composite_disk_path() const;
 
     std::string uboot_env_image_path() const;
 
-    std::string vendor_boot_image_path() const;
-
     std::string audio_server_path() const;
 
     // modem simulator related
@@ -447,12 +483,29 @@
     // Whether this instance should start the webrtc signaling server
     bool start_webrtc_sig_server() const;
 
+    // Whether to start a reverse proxy to the webrtc signaling server already
+    // running in the host
+    bool start_webrtc_sig_server_proxy() const;
+
+    // Whether this instance should start the wmediumd process
+    bool start_wmediumd() const;
+
+    // Whether this instance should start a rootcanal instance
+    bool start_rootcanal() const;
+
+    // Whether this instance should start an ap instance
+    bool start_ap() const;
+
     // Wifi MAC address inside the guest
-    std::array<unsigned char, 6> wifi_mac_address() const;
+    int wifi_mac_prefix() const;
 
     std::string factory_reset_protected_path() const;
 
     std::string persistent_bootconfig_path() const;
+
+    std::string vbmeta_path() const;
+
+    std::string id() const;
   };
 
   // A view into an existing CuttlefishConfig object for a particular instance.
@@ -461,13 +514,12 @@
     std::string id_;
     friend MutableInstanceSpecific CuttlefishConfig::ForInstance(int num);
 
-    MutableInstanceSpecific(CuttlefishConfig* config, const std::string& id)
-        : config_(config), id_(id) {}
+    MutableInstanceSpecific(CuttlefishConfig* config, const std::string& id);
 
     Json::Value* Dictionary();
   public:
     void set_serial_number(const std::string& serial_number);
-    void set_vnc_server_port(int vnc_server_port);
+    void set_qemu_vnc_server_port(int qemu_vnc_server_port);
     void set_tombstone_receiver_port(int tombstone_receiver_port);
     void set_config_server_port(int config_server_port);
     void set_frames_server_port(int config_server_port);
@@ -477,16 +529,11 @@
     void set_keymaster_vsock_port(int keymaster_vsock_port);
     void set_vehicle_hal_server_port(int vehicle_server_port);
     void set_audiocontrol_server_port(int audiocontrol_server_port);
-    void set_host_port(int host_port);
+    void set_adb_host_port(int adb_host_port);
+    void set_modem_simulator_host_id(int modem_simulator_id);
     void set_adb_ip_and_port(const std::string& ip_port);
-    void set_rootcanal_hci_port(int rootcanal_hci_port);
-    void set_rootcanal_link_port(int rootcanal_link_port);
-    void set_rootcanal_test_port(int rootcanal_test_port);
+    void set_confui_host_vsock_port(int confui_host_port);
     void set_camera_server_port(int camera_server_port);
-    void set_rootcanal_config_file(const std::string& rootcanal_config_file);
-    void set_rootcanal_default_commands_file(
-        const std::string& rootcanal_default_commands_file);
-    void set_device_title(const std::string& title);
     void set_mobile_bridge_name(const std::string& mobile_bridge_name);
     void set_mobile_tap_name(const std::string& mobile_tap_name);
     void set_wifi_tap_name(const std::string& wifi_tap_name);
@@ -495,14 +542,17 @@
     void set_use_allocd(bool use_allocd);
     void set_vsock_guest_cid(int vsock_guest_cid);
     void set_uuid(const std::string& uuid);
-    void set_instance_dir(const std::string& instance_dir);
     // modem simulator related
     void set_modem_simulator_ports(const std::string& modem_simulator_ports);
     void set_virtual_disk_paths(const std::vector<std::string>& disk_paths);
     void set_webrtc_device_id(const std::string& id);
     void set_start_webrtc_signaling_server(bool start);
+    void set_start_webrtc_sig_server_proxy(bool start);
+    void set_start_wmediumd(bool start);
+    void set_start_rootcanal(bool start);
+    void set_start_ap(bool start);
     // Wifi MAC address inside the guest
-    void set_wifi_mac_address(const std::array<unsigned char, 6>&);
+    void set_wifi_mac_prefix(const int wifi_mac_prefix);
     // Gnss grpc proxy server port inside the host
     void set_gnss_grpc_proxy_server_port(int gnss_grpc_proxy_server_port);
     // Gnss grpc proxy local file path
@@ -514,7 +564,7 @@
 
   void SetPath(const std::string& key, const std::string& path);
   bool LoadFromFile(const char* file);
-  static CuttlefishConfig* BuildConfigImpl();
+  static CuttlefishConfig* BuildConfigImpl(const std::string& path);
 
   CuttlefishConfig(const CuttlefishConfig&) = delete;
   CuttlefishConfig& operator=(const CuttlefishConfig&) = delete;
@@ -561,4 +611,9 @@
 extern const char* const kGpuModeGuestSwiftshader;
 extern const char* const kGpuModeDrmVirgl;
 extern const char* const kGpuModeGfxStream;
+
+// HwComposer modes
+extern const char* const kHwComposerAuto;
+extern const char* const kHwComposerDrm;
+extern const char* const kHwComposerRanchu;
 }  // namespace cuttlefish
diff --git a/host/libs/config/cuttlefish_config_instance.cpp b/host/libs/config/cuttlefish_config_instance.cpp
index 14e7037..4f1dd33 100644
--- a/host/libs/config/cuttlefish_config_instance.cpp
+++ b/host/libs/config/cuttlefish_config_instance.cpp
@@ -26,8 +26,18 @@
 
 const char* kInstances = "instances";
 
+std::string IdToName(const std::string& id) { return kCvdNamePrefix + id; }
+
 }  // namespace
 
+static constexpr char kInstanceDir[] = "instance_dir";
+CuttlefishConfig::MutableInstanceSpecific::MutableInstanceSpecific(
+    CuttlefishConfig* config, const std::string& id)
+    : config_(config), id_(id) {
+  // Legacy for acloud
+  (*Dictionary())[kInstanceDir] = config_->InstancesPath(IdToName(id));
+}
+
 Json::Value* CuttlefishConfig::MutableInstanceSpecific::Dictionary() {
   return &(*config_->dictionary_)[kInstances][id_];
 }
@@ -36,13 +46,8 @@
   return &(*config_->dictionary_)[kInstances][id_];
 }
 
-static constexpr char kInstanceDir[] = "instance_dir";
 std::string CuttlefishConfig::InstanceSpecific::instance_dir() const {
-  return (*Dictionary())[kInstanceDir].asString();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_instance_dir(
-    const std::string& instance_dir) {
-  (*Dictionary())[kInstanceDir] = instance_dir;
+  return config_->InstancesPath(IdToName(id_));
 }
 
 std::string CuttlefishConfig::InstanceSpecific::instance_internal_dir() const {
@@ -131,6 +136,10 @@
   return AbsolutePath(PerInstancePath("access-kregistry"));
 }
 
+std::string CuttlefishConfig::InstanceSpecific::hwcomposer_pmem_path() const {
+  return AbsolutePath(PerInstancePath("hwcomposer-pmem"));
+}
+
 std::string CuttlefishConfig::InstanceSpecific::pstore_path() const {
   return AbsolutePath(PerInstancePath("pstore"));
 }
@@ -140,7 +149,7 @@
 }
 
 std::string CuttlefishConfig::InstanceSpecific::logcat_path() const {
-  return AbsolutePath(PerInstancePath("logcat"));
+  return AbsolutePath(PerInstanceLogPath("logcat"));
 }
 
 std::string CuttlefishConfig::InstanceSpecific::launcher_monitor_socket_path()
@@ -158,28 +167,24 @@
 }
 
 std::string CuttlefishConfig::InstanceSpecific::launcher_log_path() const {
-  return AbsolutePath(PerInstancePath("launcher.log"));
+  return AbsolutePath(PerInstanceLogPath("launcher.log"));
 }
 
 std::string CuttlefishConfig::InstanceSpecific::sdcard_path() const {
   return AbsolutePath(PerInstancePath("sdcard.img"));
 }
 
-std::string CuttlefishConfig::InstanceSpecific::os_composite_disk_path() const {
-  return AbsolutePath(PerInstancePath("os_composite.img"));
-}
-
 std::string CuttlefishConfig::InstanceSpecific::persistent_composite_disk_path()
     const {
   return AbsolutePath(PerInstancePath("persistent_composite.img"));
 }
 
-std::string CuttlefishConfig::InstanceSpecific::uboot_env_image_path() const {
-  return AbsolutePath(PerInstancePath("uboot_env.img"));
+std::string CuttlefishConfig::InstanceSpecific::vbmeta_path() const {
+  return AbsolutePath(PerInstancePath("vbmeta.img"));
 }
 
-std::string CuttlefishConfig::InstanceSpecific::vendor_boot_image_path() const {
-  return AbsolutePath(PerInstancePath("vendor_boot_repacked.img"));
+std::string CuttlefishConfig::InstanceSpecific::uboot_env_image_path() const {
+  return AbsolutePath(PerInstancePath("uboot_env.img"));
 }
 
 static constexpr char kMobileBridgeName[] = "mobile_bridge_name";
@@ -205,9 +210,14 @@
   (*Dictionary())[kMobileTapName] = mobile_tap_name;
 }
 
-std::string CuttlefishConfig::InstanceSpecific::confui_hal_guest_socket_path()
-    const {
-  return PerInstanceInternalPath("confui_mock_hal_guest.sock");
+static constexpr char kConfUiHostPort[] = "confirmation_ui_host_port";
+int CuttlefishConfig::InstanceSpecific::confui_host_vsock_port() const {
+  return (*Dictionary())[kConfUiHostPort].asInt();
+}
+
+void CuttlefishConfig::MutableInstanceSpecific::set_confui_host_vsock_port(
+    int port) {
+  (*Dictionary())[kConfUiHostPort] = port;
 }
 
 static constexpr char kWifiTapName[] = "wifi_tap_name";
@@ -263,12 +273,21 @@
   (*Dictionary())[kUuid] = uuid;
 }
 
-static constexpr char kHostPort[] = "host_port";
-int CuttlefishConfig::InstanceSpecific::host_port() const {
+static constexpr char kHostPort[] = "adb_host_port";
+int CuttlefishConfig::InstanceSpecific::adb_host_port() const {
   return (*Dictionary())[kHostPort].asInt();
 }
-void CuttlefishConfig::MutableInstanceSpecific::set_host_port(int host_port) {
-  (*Dictionary())[kHostPort] = host_port;
+void CuttlefishConfig::MutableInstanceSpecific::set_adb_host_port(int port) {
+  (*Dictionary())[kHostPort] = port;
+}
+
+static constexpr char kModemSimulatorId[] = "modem_simulator_host_id";
+int CuttlefishConfig::InstanceSpecific::modem_simulator_host_id() const {
+  return (*Dictionary())[kModemSimulatorId].asInt();
+}
+void CuttlefishConfig::MutableInstanceSpecific::set_modem_simulator_host_id(
+    int id) {
+  (*Dictionary())[kModemSimulatorId] = id;
 }
 
 static constexpr char kAdbIPAndPort[] = "adb_ip_and_port";
@@ -288,29 +307,13 @@
   return "NO_ADB_MODE_SET_NO_VALID_DEVICE_NAME";
 }
 
-static constexpr char kDeviceTitle[] = "device_title";
-std::string CuttlefishConfig::InstanceSpecific::device_title() const {
-  return (*Dictionary())[kDeviceTitle].asString();
+static constexpr char kQemuVncServerPort[] = "qemu_vnc_server_port";
+int CuttlefishConfig::InstanceSpecific::qemu_vnc_server_port() const {
+  return (*Dictionary())[kQemuVncServerPort].asInt();
 }
-void CuttlefishConfig::MutableInstanceSpecific::set_device_title(
-    const std::string& title) {
-  (*Dictionary())[kDeviceTitle] = title;
-}
-
-static constexpr char kVncServerPort[] = "vnc_server_port";
-int CuttlefishConfig::InstanceSpecific::vnc_server_port() const {
-  return (*Dictionary())[kVncServerPort].asInt();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_vnc_server_port(int vnc_server_port) {
-  (*Dictionary())[kVncServerPort] = vnc_server_port;
-}
-
-static constexpr char kFramesServerPort[] = "frames_server_port";
-int CuttlefishConfig::InstanceSpecific::frames_server_port() const {
-  return (*Dictionary())[kFramesServerPort].asInt();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_frames_server_port(int frames_server_port) {
-  (*Dictionary())[kFramesServerPort] = frames_server_port;
+void CuttlefishConfig::MutableInstanceSpecific::set_qemu_vnc_server_port(
+    int qemu_vnc_server_port) {
+  (*Dictionary())[kQemuVncServerPort] = qemu_vnc_server_port;
 }
 
 static constexpr char kTouchServerPort[] = "touch_server_port";
@@ -362,33 +365,6 @@
   (*Dictionary())[kConfigServerPort] = config_server_port;
 }
 
-static constexpr char kRootcanalHciPort[] = "rootcanal_hci_port";
-int CuttlefishConfig::InstanceSpecific::rootcanal_hci_port() const {
-  return (*Dictionary())[kRootcanalHciPort].asInt();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_rootcanal_hci_port(
-    int rootcanal_hci_port) {
-  (*Dictionary())[kRootcanalHciPort] = rootcanal_hci_port;
-}
-
-static constexpr char kRootcanalLinkPort[] = "rootcanal_link_port";
-int CuttlefishConfig::InstanceSpecific::rootcanal_link_port() const {
-  return (*Dictionary())[kRootcanalLinkPort].asInt();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_rootcanal_link_port(
-    int rootcanal_link_port) {
-  (*Dictionary())[kRootcanalLinkPort] = rootcanal_link_port;
-}
-
-static constexpr char kRootcanalTestPort[] = "rootcanal_test_port";
-int CuttlefishConfig::InstanceSpecific::rootcanal_test_port() const {
-  return (*Dictionary())[kRootcanalTestPort].asInt();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_rootcanal_test_port(
-    int rootcanal_test_port) {
-  (*Dictionary())[kRootcanalTestPort] = rootcanal_test_port;
-}
-
 static constexpr char kCameraServerPort[] = "camera_server_port";
 int CuttlefishConfig::InstanceSpecific::camera_server_port() const {
   return (*Dictionary())[kCameraServerPort].asInt();
@@ -398,29 +374,6 @@
   (*Dictionary())[kCameraServerPort] = camera_server_port;
 }
 
-static constexpr char kRootcanalConfigFile[] = "rootcanal_config_file";
-std::string CuttlefishConfig::InstanceSpecific::rootcanal_config_file() const {
-  return (*Dictionary())[kRootcanalConfigFile].asString();
-}
-void CuttlefishConfig::MutableInstanceSpecific::set_rootcanal_config_file(
-    const std::string& rootcanal_config_file) {
-  (*Dictionary())[kRootcanalConfigFile] =
-      DefaultHostArtifactsPath(rootcanal_config_file);
-}
-
-static constexpr char kRootcanalDefaultCommandsFile[] =
-    "rootcanal_default_commands_file";
-std::string
-CuttlefishConfig::InstanceSpecific::rootcanal_default_commands_file() const {
-  return (*Dictionary())[kRootcanalDefaultCommandsFile].asString();
-}
-void CuttlefishConfig::MutableInstanceSpecific::
-    set_rootcanal_default_commands_file(
-        const std::string& rootcanal_default_commands_file) {
-  (*Dictionary())[kRootcanalDefaultCommandsFile] =
-      DefaultHostArtifactsPath(rootcanal_default_commands_file);
-}
-
 static constexpr char kWebrtcDeviceId[] = "webrtc_device_id";
 void CuttlefishConfig::MutableInstanceSpecific::set_webrtc_device_id(
     const std::string& id) {
@@ -438,6 +391,40 @@
   return (*Dictionary())[kStartSigServer].asBool();
 }
 
+static constexpr char kStartSigServerProxy[] = "webrtc_start_sig_server_proxy";
+void CuttlefishConfig::MutableInstanceSpecific::
+    set_start_webrtc_sig_server_proxy(bool start) {
+  (*Dictionary())[kStartSigServerProxy] = start;
+}
+bool CuttlefishConfig::InstanceSpecific::start_webrtc_sig_server_proxy() const {
+  return (*Dictionary())[kStartSigServerProxy].asBool();
+}
+
+static constexpr char kStartWmediumd[] = "start_wmediumd";
+void CuttlefishConfig::MutableInstanceSpecific::set_start_wmediumd(bool start) {
+  (*Dictionary())[kStartWmediumd] = start;
+}
+bool CuttlefishConfig::InstanceSpecific::start_wmediumd() const {
+  return (*Dictionary())[kStartWmediumd].asBool();
+}
+
+static constexpr char kStartRootcanal[] = "start_rootcanal";
+void CuttlefishConfig::MutableInstanceSpecific::set_start_rootcanal(
+    bool start) {
+  (*Dictionary())[kStartRootcanal] = start;
+}
+bool CuttlefishConfig::InstanceSpecific::start_rootcanal() const {
+  return (*Dictionary())[kStartRootcanal].asBool();
+}
+
+static constexpr char kStartAp[] = "start_ap";
+void CuttlefishConfig::MutableInstanceSpecific::set_start_ap(bool start) {
+  (*Dictionary())[kStartAp] = start;
+}
+bool CuttlefishConfig::InstanceSpecific::start_ap() const {
+  return (*Dictionary())[kStartAp].asBool();
+}
+
 std::string CuttlefishConfig::InstanceSpecific::touch_socket_path(
     int screen_idx) const {
   return PerInstanceInternalPath(
@@ -456,26 +443,13 @@
   return PerInstanceInternalPath("frames.sock");
 }
 
-static constexpr char kWifiMacAddress[] = "wifi_mac_address";
-void CuttlefishConfig::MutableInstanceSpecific::set_wifi_mac_address(
-    const std::array<unsigned char, 6>& mac_address) {
-  Json::Value mac_address_obj(Json::arrayValue);
-  for (const auto& num : mac_address) {
-    mac_address_obj.append(num);
-  }
-  (*Dictionary())[kWifiMacAddress] = mac_address_obj;
+static constexpr char kWifiMacPrefix[] = "wifi_mac_prefix";
+int CuttlefishConfig::InstanceSpecific::wifi_mac_prefix() const {
+  return (*Dictionary())[kWifiMacPrefix].asInt();
 }
-std::array<unsigned char, 6> CuttlefishConfig::InstanceSpecific::wifi_mac_address() const {
-  std::array<unsigned char, 6> mac_address{0, 0, 0, 0, 0, 0};
-  auto mac_address_obj = (*Dictionary())[kWifiMacAddress];
-  if (mac_address_obj.size() != 6) {
-    LOG(ERROR) << kWifiMacAddress << " entry had wrong size";
-    return {};
-  }
-  for (int i = 0; i < 6; i++) {
-    mac_address[i] = mac_address_obj[i].asInt();
-  }
-  return mac_address;
+void CuttlefishConfig::MutableInstanceSpecific::set_wifi_mac_prefix(
+    int wifi_mac_prefix) {
+  (*Dictionary())[kWifiMacPrefix] = wifi_mac_prefix;
 }
 
 std::string CuttlefishConfig::InstanceSpecific::factory_reset_protected_path() const {
@@ -502,8 +476,20 @@
   return PerInstancePath(relative_path.c_str());
 }
 
-std::string CuttlefishConfig::InstanceSpecific::instance_name() const {
-  return "cvd-" + id_;
+std::string CuttlefishConfig::InstanceSpecific::PerInstanceLogPath(
+    const std::string& file_name) const {
+  if (file_name.size() == 0) {
+    // Don't append a / if file_name is empty.
+    return PerInstancePath(kLogDirName);
+  }
+  auto relative_path = (std::string(kLogDirName) + "/") + file_name;
+  return PerInstancePath(relative_path.c_str());
 }
 
+std::string CuttlefishConfig::InstanceSpecific::instance_name() const {
+  return IdToName(id_);
+}
+
+std::string CuttlefishConfig::InstanceSpecific::id() const { return id_; }
+
 }  // namespace cuttlefish
diff --git a/host/libs/config/data_image.cpp b/host/libs/config/data_image.cpp
index 044dadb..8932c7c 100644
--- a/host/libs/config/data_image.cpp
+++ b/host/libs/config/data_image.cpp
@@ -1,13 +1,16 @@
 #include "host/libs/config/data_image.h"
 
 #include <android-base/logging.h>
+#include <android-base/result.h>
+
+#include "blkid.h"
 
 #include "common/libs/fs/shared_buf.h"
-
 #include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
 #include "common/libs/utils/subprocess.h"
-
 #include "host/libs/config/mbr.h"
+#include "host/libs/vm_manager/gem5_manager.h"
 
 namespace cuttlefish {
 
@@ -20,18 +23,77 @@
 const int FSCK_ERROR_CORRECTED = 1;
 const int FSCK_ERROR_CORRECTED_REQUIRES_REBOOT = 2;
 
-bool ForceFsckImage(const char* data_image) {
-  auto fsck_path = HostBinaryPath("fsck.f2fs");
+// Currently the Cuttlefish bootloaders are built only for x86 (32-bit),
+// ARM (QEMU only, 32-bit) and AArch64 (64-bit), and U-Boot will hard-code
+// these search paths. Install all bootloaders to one of these paths.
+// NOTE: For now, just ignore the 32-bit ARM version, as Debian doesn't
+//       build an EFI monolith for this architecture.
+const std::string kBootPathIA32 = "EFI/BOOT/BOOTIA32.EFI";
+const std::string kBootPathAA64 = "EFI/BOOT/BOOTAA64.EFI";
+const std::string kM5 = "";
+
+// These are the paths Debian installs the monoliths to. If another distro
+// uses an alternative monolith path, add it to this table
+const std::pair<std::string, std::string> kGrubBlobTable[] = {
+    {"/usr/lib/grub/i386-efi/monolithic/grubia32.efi", kBootPathIA32},
+    {"/usr/lib/grub/arm64-efi/monolithic/grubaa64.efi", kBootPathAA64},
+};
+
+// M5 checkpoint required binary file
+const std::pair<std::string, std::string> kM5BlobTable[] = {
+    {"/tmp/m5", kM5},
+};
+
+bool ForceFsckImage(const CuttlefishConfig& config,
+                    const std::string& data_image) {
+  std::string fsck_path;
+  if (config.userdata_format() == "f2fs") {
+    fsck_path = HostBinaryPath("fsck.f2fs");
+  } else if (config.userdata_format() == "ext4") {
+    fsck_path = "/sbin/e2fsck";
+  }
   int fsck_status = execute({fsck_path, "-y", "-f", data_image});
   if (fsck_status & ~(FSCK_ERROR_CORRECTED|FSCK_ERROR_CORRECTED_REQUIRES_REBOOT)) {
-    LOG(ERROR) << "`fsck.f2fs -y -f " << data_image << "` failed with code "
+    LOG(ERROR) << "`" << fsck_path << " -y -f " << data_image << "` failed with code "
                << fsck_status;
     return false;
   }
   return true;
 }
 
-bool ResizeImage(const char* data_image, int data_image_mb) {
+bool NewfsMsdos(const std::string& data_image, int data_image_mb,
+                int offset_num_mb) {
+  off_t image_size_bytes = static_cast<off_t>(data_image_mb) << 20;
+  off_t offset_size_bytes = static_cast<off_t>(offset_num_mb) << 20;
+  image_size_bytes -= offset_size_bytes;
+  off_t image_size_sectors = image_size_bytes / 512;
+  auto newfs_msdos_path = HostBinaryPath("newfs_msdos");
+  return execute({newfs_msdos_path,
+                         "-F",
+                         "32",
+                         "-m",
+                         "0xf8",
+                         "-o",
+                         "0",
+                         "-c",
+                         "8",
+                         "-h",
+                         "255",
+                         "-u",
+                         "63",
+                         "-S",
+                         "512",
+                         "-s",
+                         std::to_string(image_size_sectors),
+                         "-C",
+                         std::to_string(data_image_mb) + "M",
+                         "-@",
+                         std::to_string(offset_size_bytes),
+                         data_image}) == 0;
+}
+
+bool ResizeImage(const CuttlefishConfig& config, const std::string& data_image,
+                 int data_image_mb) {
   auto file_mb = FileSize(data_image) >> 20;
   if (file_mb > data_image_mb) {
     LOG(ERROR) << data_image << " is already " << file_mb << " MB, will not "
@@ -48,18 +110,23 @@
                   << data_image << "` failed:" << fd->StrError();
       return false;
     }
-    bool fsck_success = ForceFsckImage(data_image);
+    bool fsck_success = ForceFsckImage(config, data_image);
     if (!fsck_success) {
       return false;
     }
-    auto resize_path = HostBinaryPath("resize.f2fs");
+    std::string resize_path;
+    if (config.userdata_format() == "f2fs") {
+      resize_path = HostBinaryPath("resize.f2fs");
+    } else if (config.userdata_format() == "ext4") {
+      resize_path = "/sbin/resize2fs";
+    }
     int resize_status = execute({resize_path, data_image});
     if (resize_status != 0) {
-      LOG(ERROR) << "`resize.f2fs " << data_image << "` failed with code "
+      LOG(ERROR) << "`" << resize_path << " " << data_image << "` failed with code "
                  << resize_status;
       return false;
     }
-    fsck_success = ForceFsckImage(data_image);
+    fsck_success = ForceFsckImage(config, data_image);
     if (!fsck_success) {
       return false;
     }
@@ -68,7 +135,7 @@
 }
 } // namespace
 
-void CreateBlankImage(
+bool CreateBlankImage(
     const std::string& image, int num_mb, const std::string& image_fmt) {
   LOG(DEBUG) << "Creating " << image;
 
@@ -80,124 +147,420 @@
     if (fd->Truncate(image_size_bytes) != 0) {
       LOG(ERROR) << "`truncate --size=" << num_mb << "M " << image
                  << "` failed:" << fd->StrError();
-      return;
+      return false;
     }
   }
 
   if (image_fmt == "ext4") {
-    execute({"/sbin/mkfs.ext4", image});
+    if (execute({"/sbin/mkfs.ext4", image}) != 0) {
+      return false;
+    }
   } else if (image_fmt == "f2fs") {
     auto make_f2fs_path = cuttlefish::HostBinaryPath("make_f2fs");
-    execute({make_f2fs_path, "-t", image_fmt, image, "-C", "utf8", "-O",
-             "compression,extra_attr,prjquota", "-g", "android"});
+    if (execute({make_f2fs_path, "-t", image_fmt, image, "-C", "utf8", "-O",
+             "compression,extra_attr,project_quota", "-g", "android"}) != 0) {
+      return false;
+    }
   } else if (image_fmt == "sdcard") {
     // Reserve 1MB in the image for the MBR and padding, to simulate what
     // other OSes do by default when partitioning a drive
     off_t offset_size_bytes = 1 << 20;
     image_size_bytes -= offset_size_bytes;
-    off_t image_size_sectors = image_size_bytes / 512;
-    auto newfs_msdos_path = HostBinaryPath("newfs_msdos");
-    execute({newfs_msdos_path, "-F", "32", "-m", "0xf8", "-a", "4088",
-                               "-o", "0",  "-c", "8",    "-h", "255",
-                               "-u", "63", "-S", "512",
-                               "-s", std::to_string(image_size_sectors),
-                               "-C", std::to_string(num_mb) + "M",
-                               "-@", std::to_string(offset_size_bytes),
-                               image});
+    if (!NewfsMsdos(image, num_mb, 1)) {
+      LOG(ERROR) << "Failed to create SD-Card filesystem";
+      return false;
+    }
     // Write the MBR after the filesystem is formatted, as the formatting tools
     // don't consistently preserve the image contents
     MasterBootRecord mbr = {
-      .partitions = {{
-        .partition_type = 0xC,
-        .first_lba = (std::uint32_t) offset_size_bytes / SECTOR_SIZE,
-        .num_sectors = (std::uint32_t) image_size_bytes / SECTOR_SIZE,
-      }},
-      .boot_signature = { 0x55, 0xAA },
+        .partitions = {{
+            .partition_type = 0xC,
+            .first_lba = (std::uint32_t) offset_size_bytes / SECTOR_SIZE,
+            .num_sectors = (std::uint32_t) image_size_bytes / SECTOR_SIZE,
+        }},
+        .boot_signature = {0x55, 0xAA},
     };
     auto fd = SharedFD::Open(image, O_RDWR);
     if (WriteAllBinary(fd, &mbr) != sizeof(MasterBootRecord)) {
       LOG(ERROR) << "Writing MBR to " << image << " failed:" << fd->StrError();
-      return;
+      return false;
     }
   } else if (image_fmt != "none") {
     LOG(WARNING) << "Unknown image format '" << image_fmt
                  << "' for " << image << ", treating as 'none'.";
   }
+  return true;
 }
 
-DataImageResult ApplyDataImagePolicy(const CuttlefishConfig& config,
-                                     const std::string& data_image) {
-  bool data_exists = FileHasContent(data_image.c_str());
-  bool remove{};
-  bool create{};
-  bool resize{};
-
-  if (config.data_policy() == kDataPolicyUseExisting) {
-    if (!data_exists) {
-      LOG(ERROR) << "Specified data image file does not exists: " << data_image;
-      return DataImageResult::Error;
-    }
-    if (config.blank_data_image_mb() > 0) {
-      LOG(ERROR) << "You should NOT use -blank_data_image_mb with -data_policy="
-                 << kDataPolicyUseExisting;
-      return DataImageResult::Error;
-    }
-    create = false;
-    remove = false;
-    resize = false;
-  } else if (config.data_policy() == kDataPolicyAlwaysCreate) {
-    remove = data_exists;
-    create = true;
-    resize = false;
-  } else if (config.data_policy() == kDataPolicyCreateIfMissing) {
-    create = !data_exists;
-    remove = false;
-    resize = false;
-  } else if (config.data_policy() == kDataPolicyResizeUpTo) {
-    create = false;
-    remove = false;
-    resize = true;
-  } else {
-    LOG(ERROR) << "Invalid data_policy: " << config.data_policy();
-    return DataImageResult::Error;
+std::string GetFsType(const std::string& path) {
+  std::string fs_type;
+  blkid_cache cache;
+  if (blkid_get_cache(&cache, NULL) < 0) {
+    LOG(INFO) << "blkid_get_cache failed";
+    return fs_type;
+  }
+  blkid_dev dev = blkid_get_dev(cache, path.c_str(), BLKID_DEV_NORMAL);
+  if (!dev) {
+    LOG(INFO) << "blkid_get_dev failed";
+    blkid_put_cache(cache);
+    return fs_type;
   }
 
-  if (remove) {
-    RemoveFile(data_image.c_str());
-  }
-
-  if (create) {
-    if (config.blank_data_image_mb() <= 0) {
-      LOG(ERROR) << "-blank_data_image_mb is required to create data image";
-      return DataImageResult::Error;
+  const char *type, *value;
+  blkid_tag_iterate iter = blkid_tag_iterate_begin(dev);
+  while (blkid_tag_next(iter, &type, &value) == 0) {
+    if (!strcmp(type, "TYPE")) {
+      fs_type = value;
     }
-    CreateBlankImage(data_image.c_str(), config.blank_data_image_mb(),
-                     config.blank_data_image_fmt());
-    return DataImageResult::FileUpdated;
-  } else if (resize) {
-    if (!data_exists) {
-      LOG(ERROR) << data_image << " does not exist, but resizing was requested";
-      return DataImageResult::Error;
-    }
-    bool success = ResizeImage(data_image.c_str(), config.blank_data_image_mb());
-    return success ? DataImageResult::FileUpdated : DataImageResult::Error;
-  } else {
-    LOG(DEBUG) << data_image << " exists. Not creating it.";
-    return DataImageResult::NoChange;
   }
+  blkid_tag_iterate_end(iter);
+  blkid_put_cache(cache);
+  return fs_type;
 }
 
-bool InitializeMiscImage(const std::string& misc_image) {
-  bool misc_exists = FileHasContent(misc_image.c_str());
+struct DataImageTag {};
 
-  if (misc_exists) {
-    LOG(DEBUG) << "misc partition image: use existing";
+class FixedDataImagePath : public DataImagePath {
+ public:
+  INJECT(FixedDataImagePath(ANNOTATED(DataImageTag, std::string) path))
+      : path_(path) {}
+
+  const std::string& Path() const override { return path_; }
+
+ private:
+  std::string path_;
+};
+
+fruit::Component<DataImagePath> FixedDataImagePathComponent(
+    const std::string* path) {
+  return fruit::createComponent()
+      .bind<DataImagePath, FixedDataImagePath>()
+      .bindInstance<fruit::Annotated<DataImageTag, std::string>>(*path);
+}
+
+class InitializeDataImageImpl : public InitializeDataImage {
+ public:
+  INJECT(InitializeDataImageImpl(const CuttlefishConfig& config,
+                                 DataImagePath& data_path))
+      : config_(config), data_path_(data_path) {}
+
+  // SetupFeature
+  std::string Name() const override { return "InitializeDataImageImpl"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override {
+    auto action = ChooseAction();
+    if (!action.ok()) {
+      LOG(ERROR) << "Failed to select a userdata processing action: "
+                 << action.error();
+      return false;
+    }
+    auto result = EvaluateAction(*action);
+    if (!result.ok()) {
+      LOG(ERROR) << "Failed to evaluate userdata action: " << result.error();
+      return false;
+    }
     return true;
   }
 
-  LOG(DEBUG) << "misc partition image: creating empty";
-  CreateBlankImage(misc_image, 1 /* mb */, "none");
-  return true;
+ private:
+  enum class DataImageAction { kNoAction, kCreateImage, kResizeImage };
+
+  Result<DataImageAction> ChooseAction() {
+    if (config_.data_policy() == kDataPolicyAlwaysCreate) {
+      return DataImageAction::kCreateImage;
+    }
+    if (!FileHasContent(data_path_.Path())) {
+      if (config_.data_policy() == kDataPolicyUseExisting) {
+        return CF_ERR("A data image must exist to use -data_policy="
+                      << kDataPolicyUseExisting);
+      } else if (config_.data_policy() == kDataPolicyResizeUpTo) {
+        return CF_ERR(data_path_.Path()
+                      << " does not exist, but resizing was requested");
+      }
+      return DataImageAction::kCreateImage;
+    }
+    if (GetFsType(data_path_.Path()) != config_.userdata_format()) {
+      CF_EXPECT(config_.data_policy() == kDataPolicyResizeUpTo,
+                "Changing the fs format is incompatible with -data_policy="
+                    << kDataPolicyResizeUpTo);
+      return DataImageAction::kCreateImage;
+    }
+    if (config_.data_policy() == kDataPolicyResizeUpTo) {
+      return DataImageAction::kResizeImage;
+    }
+    return DataImageAction::kNoAction;
+  }
+
+  Result<void> EvaluateAction(DataImageAction action) {
+    switch (action) {
+      case DataImageAction::kNoAction:
+        LOG(DEBUG) << data_path_.Path() << " exists. Not creating it.";
+        return {};
+      case DataImageAction::kCreateImage: {
+        RemoveFile(data_path_.Path());
+        CF_EXPECT(config_.blank_data_image_mb() != 0,
+                  "Expected `-blank_data_image_mb` to be set for "
+                  "image creation.");
+        CF_EXPECT(
+            CreateBlankImage(data_path_.Path(), config_.blank_data_image_mb(),
+                             config_.userdata_format()),
+            "Failed to create a blank image at \""
+                << data_path_.Path() << "\" with size "
+                << config_.blank_data_image_mb() << " and format \""
+                << config_.userdata_format() << "\"");
+        return {};
+      }
+      case DataImageAction::kResizeImage: {
+        CF_EXPECT(config_.blank_data_image_mb() != 0,
+                  "Expected `-blank_data_image_mb` to be set for "
+                  "image resizing.");
+        CF_EXPECT(ResizeImage(config_, data_path_.Path(),
+                              config_.blank_data_image_mb()),
+                  "Failed to resize \"" << data_path_.Path() << "\" to "
+                                        << config_.blank_data_image_mb()
+                                        << " MB");
+        return {};
+      }
+    }
+  }
+
+  const CuttlefishConfig& config_;
+  DataImagePath& data_path_;
+};
+
+fruit::Component<fruit::Required<const CuttlefishConfig, DataImagePath>,
+                 InitializeDataImage>
+InitializeDataImageComponent() {
+  return fruit::createComponent()
+      .addMultibinding<SetupFeature, InitializeDataImage>()
+      .bind<InitializeDataImage, InitializeDataImageImpl>();
+}
+
+struct MiscImageTag {};
+
+class FixedMiscImagePath : public MiscImagePath {
+ public:
+  INJECT(FixedMiscImagePath(ANNOTATED(MiscImageTag, std::string) path))
+      : path_(path) {}
+
+  const std::string& Path() const override { return path_; }
+
+ private:
+  std::string path_;
+};
+
+class InitializeMiscImageImpl : public InitializeMiscImage {
+ public:
+  INJECT(InitializeMiscImageImpl(MiscImagePath& misc_path))
+      : misc_path_(misc_path) {}
+
+  // SetupFeature
+  std::string Name() const override { return "InitializeMiscImageImpl"; }
+  bool Enabled() const override { return true; }
+
+ private:
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Setup() override {
+    bool misc_exists = FileHasContent(misc_path_.Path());
+
+    if (misc_exists) {
+      LOG(DEBUG) << "misc partition image: use existing at \""
+                 << misc_path_.Path() << "\"";
+      return true;
+    }
+
+    LOG(DEBUG) << "misc partition image: creating empty at \""
+               << misc_path_.Path() << "\"";
+    if (!CreateBlankImage(misc_path_.Path(), 1 /* mb */, "none")) {
+      LOG(ERROR) << "Failed to create misc image";
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  MiscImagePath& misc_path_;
+};
+
+fruit::Component<MiscImagePath> FixedMiscImagePathComponent(
+    const std::string* path) {
+  return fruit::createComponent()
+      .bind<MiscImagePath, FixedMiscImagePath>()
+      .bindInstance<fruit::Annotated<MiscImageTag, std::string>>(*path);
+}
+
+fruit::Component<fruit::Required<MiscImagePath>, InitializeMiscImage>
+InitializeMiscImageComponent() {
+  return fruit::createComponent()
+      .addMultibinding<SetupFeature, InitializeMiscImage>()
+      .bind<InitializeMiscImage, InitializeMiscImageImpl>();
+}
+
+struct EspImageTag {};
+struct KernelPathTag {};
+struct InitRamFsTag {};
+struct RootFsTag {};
+struct ConfigTag {};
+
+class InitializeEspImageImpl : public InitializeEspImage {
+ public:
+  INJECT(InitializeEspImageImpl(ANNOTATED(EspImageTag, std::string) esp_image,
+                                ANNOTATED(KernelPathTag, std::string)
+                                    kernel_path,
+                                ANNOTATED(InitRamFsTag, std::string)
+                                    initramfs_path,
+                                ANNOTATED(RootFsTag, std::string) rootfs_path,
+                                ANNOTATED(ConfigTag, const CuttlefishConfig *) config))
+      : esp_image_(esp_image),
+        kernel_path_(kernel_path),
+        initramfs_path_(initramfs_path),
+        rootfs_path_(rootfs_path),
+        config_(config){}
+
+  // SetupFeature
+  std::string Name() const override { return "InitializeEspImageImpl"; }
+  std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+  bool Enabled() const override { return !rootfs_path_.empty(); }
+
+ protected:
+  bool Setup() override {
+    bool esp_exists = FileHasContent(esp_image_);
+    if (esp_exists) {
+      LOG(DEBUG) << "esp partition image: use existing";
+      return true;
+    }
+
+    LOG(DEBUG) << "esp partition image: creating default";
+
+    // newfs_msdos won't make a partition smaller than 257 mb
+    // this should be enough for anybody..
+    auto tmp_esp_image = esp_image_ + ".tmp";
+    if (!NewfsMsdos(tmp_esp_image, 257 /* mb */, 0 /* mb (offset) */)) {
+      LOG(ERROR) << "Failed to create filesystem for " << tmp_esp_image;
+      return false;
+    }
+
+    // For licensing and build reproducibility reasons, pick up the bootloaders
+    // from the host Linux distribution (if present) and pack them into the
+    // automatically generated ESP. If the user wants their own bootloaders,
+    // they can use -esp_image=/path/to/esp.img to override, so we don't need
+    // to accommodate customizations of this packing process.
+
+    int success;
+    const std::pair<std::string, std::string> *kBlobTable;
+    std::size_t size;
+    // Skip GRUB on Gem5
+    if (config_->vm_manager() != vm_manager::Gem5Manager::name()){
+      // Currently we only support Debian based distributions, and GRUB is built
+      // for those distros to always load grub.cfg from EFI/debian/grub.cfg, and
+      // nowhere else. If you want to add support for other distros, make the
+      // extra directories below and copy the initial grub.cfg there as well
+      auto mmd = HostBinaryPath("mmd");
+      success =
+          execute({mmd, "-i", tmp_esp_image, "EFI", "EFI/BOOT", "EFI/debian"});
+      if (success != 0) {
+        LOG(ERROR) << "Failed to create directories in " << tmp_esp_image;
+        return false;
+      }
+      size = sizeof(kGrubBlobTable)/sizeof(const std::pair<std::string, std::string>);
+      kBlobTable = kGrubBlobTable;
+    } else {
+      size = sizeof(kM5BlobTable)/sizeof(const std::pair<std::string, std::string>);
+      kBlobTable = kM5BlobTable;
+    }
+
+    // The grub binaries are small, so just copy all the architecture blobs
+    // we can find, which minimizes complexity. If the user removed the grub bin
+    // package from their system, the ESP will be empty and Other OS will not be
+    // supported
+    auto mcopy = HostBinaryPath("mcopy");
+    bool copied = false;
+    for (int i=0; i<size; i++) {
+      auto grub = kBlobTable[i];
+      if (!FileExists(grub.first)) {
+        continue;
+      }
+      success = execute({mcopy, "-o", "-i", tmp_esp_image, "-s", grub.first,
+                         "::" + grub.second});
+      if (success != 0) {
+        LOG(ERROR) << "Failed to copy " << grub.first << " to " << grub.second
+                   << " in " << tmp_esp_image;
+        return false;
+      }
+      copied = true;
+    }
+
+    if (!copied) {
+      LOG(ERROR) << "Binary dependencies were not found on this system; Other OS "
+                    "support will be broken";
+      return false;
+    }
+
+    // Skip Gem5 case. Gem5 will never be able to use bootloaders like grub.
+    if (config_->vm_manager() != vm_manager::Gem5Manager::name()){
+      auto grub_cfg = DefaultHostArtifactsPath("etc/grub/grub.cfg");
+      CHECK(FileExists(grub_cfg)) << "Missing file " << grub_cfg << "!";
+      success =
+          execute({mcopy, "-i", tmp_esp_image, "-s", grub_cfg, "::EFI/debian/"});
+      if (success != 0) {
+        LOG(ERROR) << "Failed to copy " << grub_cfg << " to " << tmp_esp_image;
+        return false;
+      }
+    }
+
+    if (!kernel_path_.empty()) {
+      success = execute(
+          {mcopy, "-i", tmp_esp_image, "-s", kernel_path_, "::vmlinuz"});
+      if (success != 0) {
+        LOG(ERROR) << "Failed to copy " << kernel_path_ << " to "
+                   << tmp_esp_image;
+        return false;
+      }
+
+      if (!initramfs_path_.empty()) {
+        success = execute({mcopy, "-i", tmp_esp_image, "-s", initramfs_path_,
+                           "::initrd.img"});
+        if (success != 0) {
+          LOG(ERROR) << "Failed to copy " << initramfs_path_ << " to "
+                     << tmp_esp_image;
+          return false;
+        }
+      }
+    }
+
+    if (!cuttlefish::RenameFile(tmp_esp_image, esp_image_)) {
+      LOG(ERROR) << "Renaming " << tmp_esp_image << " to " << esp_image_
+                 << " failed";
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  std::string esp_image_;
+  std::string kernel_path_;
+  std::string initramfs_path_;
+  std::string rootfs_path_;
+  const CuttlefishConfig* config_;
+};
+
+fruit::Component<fruit::Required<const CuttlefishConfig>,
+    InitializeEspImage> InitializeEspImageComponent(
+    const std::string* esp_image, const std::string* kernel_path,
+    const std::string* initramfs_path, const std::string* rootfs_path,
+    const CuttlefishConfig* config) {
+  return fruit::createComponent()
+      .addMultibinding<SetupFeature, InitializeEspImage>()
+      .bind<InitializeEspImage, InitializeEspImageImpl>()
+      .bindInstance<fruit::Annotated<EspImageTag, std::string>>(*esp_image)
+      .bindInstance<fruit::Annotated<KernelPathTag, std::string>>(*kernel_path)
+      .bindInstance<fruit::Annotated<InitRamFsTag, std::string>>(
+          *initramfs_path)
+      .bindInstance<fruit::Annotated<RootFsTag, std::string>>(*rootfs_path)
+      .bindInstance<fruit::Annotated<ConfigTag, CuttlefishConfig>>(*config);
 }
 
 } // namespace cuttlefish
diff --git a/host/libs/config/data_image.h b/host/libs/config/data_image.h
index 6a6a810..0ae9fcc 100644
--- a/host/libs/config/data_image.h
+++ b/host/libs/config/data_image.h
@@ -1,21 +1,50 @@
 #pragma once
 
 #include <string>
+//
+#include <fruit/fruit.h>
 
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/feature.h"
 
 namespace cuttlefish {
 
-enum class DataImageResult {
-  Error,
-  NoChange,
-  FileUpdated,
+class DataImagePath {
+ public:
+  virtual ~DataImagePath() = default;
+  virtual const std::string& Path() const = 0;
 };
 
-DataImageResult ApplyDataImagePolicy(const CuttlefishConfig& config,
-                                     const std::string& path);
-bool InitializeMiscImage(const std::string& misc_image);
-void CreateBlankImage(
+class InitializeDataImage : public SetupFeature {};
+
+fruit::Component<DataImagePath> FixedDataImagePathComponent(
+    const std::string* path);
+fruit::Component<fruit::Required<const CuttlefishConfig, DataImagePath>,
+                 InitializeDataImage>
+InitializeDataImageComponent();
+
+class InitializeEspImage : public SetupFeature {};
+
+fruit::Component<fruit::Required<const CuttlefishConfig>,
+    InitializeEspImage> InitializeEspImageComponent(
+    const std::string* esp_image, const std::string* kernel_path,
+    const std::string* initramfs_path, const std::string* root_fs,
+    const CuttlefishConfig* config);
+
+bool CreateBlankImage(
     const std::string& image, int num_mb, const std::string& image_fmt);
 
+class MiscImagePath {
+ public:
+  virtual ~MiscImagePath() = default;
+  virtual const std::string& Path() const = 0;
+};
+
+class InitializeMiscImage : public SetupFeature {};
+
+fruit::Component<MiscImagePath> FixedMiscImagePathComponent(
+    const std::string* path);
+fruit::Component<fruit::Required<MiscImagePath>, InitializeMiscImage>
+InitializeMiscImageComponent();
+
 } // namespace cuttlefish
diff --git a/host/libs/config/feature.cpp b/host/libs/config/feature.cpp
new file mode 100644
index 0000000..cecf201
--- /dev/null
+++ b/host/libs/config/feature.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/config/feature.h"
+
+#include <unordered_set>
+
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+SetupFeature::~SetupFeature() {}
+
+Result<void> SetupFeature::ResultSetup() {
+  CF_EXPECT(Setup());
+  return {};
+}
+
+bool SetupFeature::Setup() {
+  LOG(ERROR) << "Missing ResultSetup implementation";
+  return false;
+}
+
+/* static */ Result<void> SetupFeature::RunSetup(
+    const std::vector<SetupFeature*>& features) {
+  std::unordered_set<SetupFeature*> enabled;
+  for (const auto& feature : features) {
+    CF_EXPECT(feature != nullptr, "Received null feature");
+    if (feature->Enabled()) {
+      enabled.insert(feature);
+    }
+  }
+  // Collect these in a vector first to trigger any obvious dependency issues.
+  std::vector<SetupFeature*> ordered_features;
+  auto add_feature = [&ordered_features](SetupFeature* feature) -> bool {
+    ordered_features.push_back(feature);
+    return true;
+  };
+  CF_EXPECT(Feature<SetupFeature>::TopologicalVisit(enabled, add_feature),
+            "Dependency issue detected, not performing any setup.");
+  // TODO(b/189153501): This can potentially be parallelized.
+  for (auto& feature : ordered_features) {
+    LOG(DEBUG) << "Running setup for " << feature->Name();
+    CF_EXPECT(feature->ResultSetup(), "Setup failed for " << feature->Name());
+  }
+  return {};
+}
+
+Result<void> FlagFeature::ProcessFlags(
+    const std::vector<FlagFeature*>& features,
+    std::vector<std::string>& flags) {
+  std::unordered_set<FlagFeature*> features_set(features.begin(),
+                                                features.end());
+  CF_EXPECT(features_set.count(nullptr) == 0, "Received null feature");
+  auto handle = [&flags](FlagFeature* feature) -> bool {
+    return feature->Process(flags);
+  };
+  CF_EXPECT(
+      Feature<FlagFeature>::TopologicalVisit(features_set, handle),
+      "Unable to parse flags.");
+  return {};
+}
+
+bool FlagFeature::WriteGflagsHelpXml(const std::vector<FlagFeature*>& features,
+                                     std::ostream& out) {
+  // Lifted from external/gflags/src/gflags_reporting.cc:ShowXMLOfFlags
+  out << "<?xml version=\"1.0\"?>\n";
+  out << "<AllFlags>\n";
+  out << "  <program>program</program>\n";
+  out << "  <usage>usage</usage>\n";
+  for (const auto& feature : features) {
+    if (!feature) {
+      LOG(ERROR) << "Received null feature";
+      return false;
+    }
+    if (!feature->WriteGflagsCompatHelpXml(out)) {
+      LOG(ERROR) << "Failure to write xml";
+      return false;
+    }
+  }
+  out << "</AllFlags>";
+  return true;
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/feature.h b/host/libs/config/feature.h
new file mode 100644
index 0000000..15590e4
--- /dev/null
+++ b/host/libs/config/feature.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <ostream>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+template <typename Subclass>
+class Feature {
+ public:
+  virtual ~Feature() = default;
+
+  virtual std::string Name() const = 0;
+
+  static Result<void> TopologicalVisit(
+      const std::unordered_set<Subclass*>& features,
+      const std::function<bool(Subclass*)>& callback);
+
+ private:
+  virtual std::unordered_set<Subclass*> Dependencies() const = 0;
+};
+
+class SetupFeature : public virtual Feature<SetupFeature> {
+ public:
+  virtual ~SetupFeature();
+
+  static Result<void> RunSetup(const std::vector<SetupFeature*>& features);
+
+  virtual bool Enabled() const = 0;
+
+ private:
+  virtual Result<void> ResultSetup();
+  virtual bool Setup();
+};
+
+class FlagFeature : public Feature<FlagFeature> {
+ public:
+  static Result<void> ProcessFlags(const std::vector<FlagFeature*>& features,
+                                   std::vector<std::string>& flags);
+  static bool WriteGflagsHelpXml(const std::vector<FlagFeature*>& features,
+                                 std::ostream& out);
+
+ private:
+  // Must be executed in dependency order following Dependencies(). Expected to
+  // mutate the `flags` argument to remove handled flags, and possibly introduce
+  // new flag values (e.g. from a file).
+  virtual bool Process(std::vector<std::string>& flags) = 0;
+
+  // TODO(schuffelen): Migrate the xml help to human-readable help output after
+  // the gflags migration is done.
+
+  // Write an xml fragment that is compatible with gflags' `--helpxml` format.
+  virtual bool WriteGflagsCompatHelpXml(std::ostream& out) const = 0;
+};
+
+template <typename Subclass>
+Result<void> Feature<Subclass>::TopologicalVisit(
+    const std::unordered_set<Subclass*>& features,
+    const std::function<bool(Subclass*)>& callback) {
+  enum class Status { UNVISITED, VISITING, VISITED };
+  std::unordered_map<Subclass*, Status> features_status;
+  for (const auto& feature : features) {
+    features_status[feature] = Status::UNVISITED;
+  }
+  std::function<Result<void>(Subclass*)> visit;
+  visit = [&callback, &features_status,
+           &visit](Subclass* feature) -> Result<void> {
+    CF_EXPECT(features_status.count(feature) > 0,
+              "Dependency edge to "
+                  << feature->Name() << " but it is not part of the feature "
+                  << "graph. This feature is either disabled or not correctly "
+                  << "registered.");
+    if (features_status[feature] == Status::VISITED) {
+      return {};
+    }
+    CF_EXPECT(features_status[feature] != Status::VISITING,
+              "Cycle detected while visiting " << feature->Name());
+    features_status[feature] = Status::VISITING;
+    for (const auto& dependency : feature->Dependencies()) {
+      CF_EXPECT(dependency != nullptr,
+                "SetupFeature " << feature->Name() << " has a null dependency.");
+      CF_EXPECT(visit(dependency),
+                "Error detected while visiting " << feature->Name());
+    }
+    features_status[feature] = Status::VISITED;
+    CF_EXPECT(callback(feature), "Callback error on " << feature->Name());
+    return {};
+  };
+  for (const auto& feature : features) {
+    CF_EXPECT(visit(feature));  // `visit` will log the error chain.
+  }
+  return {};
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/host_tools_version.cpp b/host/libs/config/host_tools_version.cpp
index 58f0657..17fceb7 100644
--- a/host/libs/config/host_tools_version.cpp
+++ b/host/libs/config/host_tools_version.cpp
@@ -29,7 +29,7 @@
 
 namespace cuttlefish {
 
-static uint32_t FileCrc(const std::string& path) {
+uint32_t FileCrc(const std::string& path) {
   uint32_t crc = crc32(0, (unsigned char*) path.c_str(), path.size());
   std::ifstream file_stream(path, std::ifstream::binary);
   std::vector<char> data(1024, 0);
diff --git a/host/libs/config/host_tools_version.h b/host/libs/config/host_tools_version.h
index 06b075e..c851692 100644
--- a/host/libs/config/host_tools_version.h
+++ b/host/libs/config/host_tools_version.h
@@ -21,6 +21,7 @@
 
 namespace cuttlefish {
 
+uint32_t FileCrc(const std::string& path);
 std::map<std::string, uint32_t> HostToolsCrc();
 
 } // namespace cuttlefish
diff --git a/host/libs/config/inject.h b/host/libs/config/inject.h
new file mode 100644
index 0000000..4e2a6e0
--- /dev/null
+++ b/host/libs/config/inject.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <fruit/fruit.h>
+#include <type_traits>
+
+namespace cuttlefish {
+
+/**
+ * This is a template helper to add bindings for a set of implementation
+ * classes that may each be part of multiple multibindings. To be more specific,
+ * for these example classes:
+ *
+ *   class ImplementationA : public IntX, IntY {};
+ *   class ImplementationB : public IntY, IntZ {};
+ *
+ * can be installed with
+ *
+ *   using Deps = fruit::Required<...>;
+ *   using Bases = Multibindings<Deps>::Bases<IntX, IntY, IntZ>;
+ *   return fruit::createComponent()
+ *     .install(Bases::Impls<ImplementationA, ImplementationB>);
+ *
+ * Note that not all implementations have to implement all interfaces. Invalid
+ * combinations are filtered out at compile-time through SFINAE.
+ */
+template <typename Deps>
+struct Multibindings {
+  /* SFINAE logic for an individual interface binding. The class does implement
+   * the interface, so add a multibinding. */
+  template <typename Base, typename Impl,
+            std::enable_if_t<std::is_base_of<Base, Impl>::value, bool> = true>
+  static fruit::Component<Deps> OneBaseOneImpl() {
+    return fruit::createComponent().addMultibinding<Base, Impl>();
+  }
+  /* SFINAE logic for an individual interface binding. The class does not
+   * implement the interface, so do not add a multibinding. */
+  template <typename Base, typename Impl,
+            std::enable_if_t<!std::is_base_of<Base, Impl>::value, bool> = true>
+  static fruit::Component<Deps> OneBaseOneImpl() {
+    return fruit::createComponent();
+  }
+
+  template <typename Base>
+  struct OneBase {
+    template <typename... ImplTypes>
+    static fruit::Component<Deps> Impls() {
+      return fruit::createComponent().installComponentFunctions(
+          fruit::componentFunction(OneBaseOneImpl<Base, ImplTypes>)...);
+    }
+  };
+
+  template <typename... BaseTypes>
+  struct Bases {
+    template <typename... ImplTypes>
+    static fruit::Component<Deps> Impls() {
+      return fruit::createComponent().installComponentFunctions(
+          fruit::componentFunction(
+              OneBase<BaseTypes>::template Impls<ImplTypes...>)...);
+    }
+  };
+};
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/kernel_args.cpp b/host/libs/config/kernel_args.cpp
index f5393d5..cbfdd5e 100644
--- a/host/libs/config/kernel_args.cpp
+++ b/host/libs/config/kernel_args.cpp
@@ -42,46 +42,34 @@
 // substitute for the vm_manager comparisons.
 std::vector<std::string> VmManagerKernelCmdline(const CuttlefishConfig& config) {
   std::vector<std::string> vm_manager_cmdline;
-  if (config.vm_manager() == QemuManager::name() || config.use_bootloader()) {
-    // crosvm sets up the console= earlycon= panic= flags for us if booting straight to
-    // the kernel, but QEMU and the bootloader via crosvm does not.
-    AppendVector(&vm_manager_cmdline, {"console=hvc0", "panic=-1"});
+  if (config.vm_manager() == QemuManager::name()) {
+    vm_manager_cmdline.push_back("console=hvc0");
     Arch target_arch = config.target_arch();
     if (target_arch == Arch::Arm64 || target_arch == Arch::Arm) {
-      if (config.vm_manager() == QemuManager::name()) {
-        // To update the pl011 address:
-        // $ qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine dumpdtb=virt.dtb
-        // $ dtc -O dts -o virt.dts -I dtb virt.dtb
-        // In the virt.dts file, look for a uart node
-        vm_manager_cmdline.push_back(" earlycon=pl011,mmio32,0x9000000");
-      } else {
-        // Crosvm ARM only supports earlycon uart over mmio.
-        vm_manager_cmdline.push_back(" earlycon=uart8250,mmio,0x3f8");
-      }
+      // To update the pl011 address:
+      // $ qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine dumpdtb=virt.dtb
+      // $ dtc -O dts -o virt.dts -I dtb virt.dtb
+      // In the virt.dts file, look for a uart node
+      vm_manager_cmdline.push_back("earlycon=pl011,mmio32,0x9000000");
     } else {
       // To update the uart8250 address:
       // $ qemu-system-x86_64 -kernel bzImage -serial stdio | grep ttyS0
       // Only 'io' mode works; mmio and mmio32 do not
       vm_manager_cmdline.push_back("earlycon=uart8250,io,0x3f8");
 
-      if (config.vm_manager() == QemuManager::name()) {
-        // crosvm doesn't support ACPI PNP, but QEMU does. We need to disable
-        // it on QEMU so that the ISA serial ports aren't claimed by ACPI, so
-        // we can use serdev with platform devices instead
-        vm_manager_cmdline.push_back("pnpacpi=off");
+      // crosvm doesn't support ACPI PNP, but QEMU does. We need to disable
+      // it on QEMU so that the ISA serial ports aren't claimed by ACPI, so
+      // we can use serdev with platform devices instead
+      vm_manager_cmdline.push_back("pnpacpi=off");
 
-        // crosvm sets up the ramoops.xx= flags for us, but QEMU does not.
-        // See external/crosvm/x86_64/src/lib.rs
-        // this feature is not supported on aarch64
-        vm_manager_cmdline.push_back("ramoops.mem_address=0x100000000");
-        vm_manager_cmdline.push_back("ramoops.mem_size=0x200000");
-        vm_manager_cmdline.push_back("ramoops.console_size=0x80000");
-        vm_manager_cmdline.push_back("ramoops.record_size=0x80000");
-        vm_manager_cmdline.push_back("ramoops.dump_oops=1");
-      } else {
-        // crosvm requires these additional parameters on x86_64 in bootloader mode
-        AppendVector(&vm_manager_cmdline, {"reboot=k"});
-      }
+      // crosvm sets up the ramoops.xx= flags for us, but QEMU does not.
+      // See external/crosvm/x86_64/src/lib.rs
+      // this feature is not supported on aarch64
+      vm_manager_cmdline.push_back("ramoops.mem_address=0x100000000");
+      vm_manager_cmdline.push_back("ramoops.mem_size=0x200000");
+      vm_manager_cmdline.push_back("ramoops.console_size=0x80000");
+      vm_manager_cmdline.push_back("ramoops.record_size=0x80000");
+      vm_manager_cmdline.push_back("ramoops.dump_oops=1");
     }
   }
 
@@ -97,23 +85,8 @@
 std::vector<std::string> KernelCommandLineFromConfig(
     const CuttlefishConfig& config) {
   std::vector<std::string> kernel_cmdline;
-
   AppendVector(&kernel_cmdline, VmManagerKernelCmdline(config));
-
-  if (config.enable_gnss_grpc_proxy()) {
-    kernel_cmdline.push_back("gnss_cmdline.serdev=serial8250/serial0/serial0-0");
-    kernel_cmdline.push_back("gnss_cmdline.type=0");
-    kernel_cmdline.push_back("serdev_ttyport.pdev_tty_port=ttyS1");
-  }
-
-  if (config.guest_audit_security()) {
-    kernel_cmdline.push_back("audit=1");
-  } else {
-    kernel_cmdline.push_back("audit=0");
-  }
-
   AppendVector(&kernel_cmdline, config.extra_kernel_cmdline());
-
   return kernel_cmdline;
 }
 
diff --git a/guest/hals/audio/Android.bp b/host/libs/config/kernel_log_pipe_provider.h
similarity index 62%
copy from guest/hals/audio/Android.bp
copy to host/libs/config/kernel_log_pipe_provider.h
index c6faae7..c9779ea 100644
--- a/guest/hals/audio/Android.bp
+++ b/host/libs/config/kernel_log_pipe_provider.h
@@ -1,3 +1,4 @@
+//
 // Copyright (C) 2019 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,16 +13,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
+#pragma once
 
-cc_library_shared {
-    name: "audio.primary.cutf",
-    relative_install_path: "hw",
-    defaults: ["cuttlefish_guest_only"],
-    vendor: true,
-    srcs: ["audio_hw.c"],
-    cflags: ["-Wno-unused-parameter"],
-    shared_libs: ["libcutils", "libhardware", "liblog", "libtinyalsa"],
-}
+#include <fruit/fruit.h>
+#include <vector>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+
+class KernelLogPipeProvider : public virtual SetupFeature {
+ public:
+  virtual ~KernelLogPipeProvider() = default;
+  virtual SharedFD KernelLogPipe() = 0;
+};
+
+}  // namespace cuttlefish
diff --git a/host/libs/config/known_paths.cpp b/host/libs/config/known_paths.cpp
index 1581a06..f45137e 100644
--- a/host/libs/config/known_paths.cpp
+++ b/host/libs/config/known_paths.cpp
@@ -53,7 +53,7 @@
 }
 
 std::string RootCanalBinary() {
-  return DefaultHostArtifactsPath("bin/root-canal");
+  return HostBinaryPath("root-canal");
 }
 
 std::string SocketVsockProxyBinary() {
@@ -64,6 +64,11 @@
   return HostBinaryPath("tombstone_receiver");
 }
 
+std::string VehicleHalGrpcServerBinary() {
+  return HostBinaryPath(
+      "android.hardware.automotive.vehicle@2.0-virtualization-grpc-server");
+}
+
 std::string WebRtcBinary() {
   return HostBinaryPath("webRTC");
 }
@@ -72,8 +77,14 @@
   return HostBinaryPath("webrtc_operator");
 }
 
-std::string VncServerBinary() {
-  return HostBinaryPath("vnc_server");
+std::string WebRtcSigServerProxyBinary() {
+  return HostBinaryPath("operator_proxy");
+}
+
+std::string WmediumdBinary() { return HostBinaryPath("wmediumd"); }
+
+std::string WmediumdGenConfigBinary() {
+  return HostBinaryPath("wmediumd_gen_config");
 }
 
 } // namespace cuttlefish
diff --git a/host/libs/config/known_paths.h b/host/libs/config/known_paths.h
index 71b6253..8c39e61 100644
--- a/host/libs/config/known_paths.h
+++ b/host/libs/config/known_paths.h
@@ -30,8 +30,11 @@
 std::string RootCanalBinary();
 std::string SocketVsockProxyBinary();
 std::string TombstoneReceiverBinary();
+std::string VehicleHalGrpcServerBinary();
 std::string WebRtcBinary();
 std::string WebRtcSigServerBinary();
-std::string VncServerBinary();
+std::string WebRtcSigServerProxyBinary();
+std::string WmediumdBinary();
+std::string WmediumdGenConfigBinary();
 
 } // namespace cuttlefish
diff --git a/host/libs/config/logging.cpp b/host/libs/config/logging.cpp
index 50a14f8..9cf1705 100644
--- a/host/libs/config/logging.cpp
+++ b/host/libs/config/logging.cpp
@@ -33,10 +33,15 @@
 
   auto instance = config->ForDefaultInstance();
 
+  std::string prefix = "";
+  if (config->Instances().size() > 1) {
+    prefix = instance.instance_name() + ": ";
+  }
+
   if (config->run_as_daemon()) {
     SetLogger(LogToFiles({instance.launcher_log_path()}));
   } else {
-    SetLogger(LogToStderrAndFiles({instance.launcher_log_path()}));
+    SetLogger(LogToStderrAndFiles({instance.launcher_log_path()}, prefix));
   }
 }
 
diff --git a/host/libs/confui/Android.bp b/host/libs/confui/Android.bp
index b74eb24..585684e 100644
--- a/host/libs/confui/Android.bp
+++ b/host/libs/confui/Android.bp
@@ -32,18 +32,24 @@
 cc_library_static {
     name: "libcuttlefish_confui_host",
     srcs: [
+        "cbor.cc",
         "host_renderer.cc",
         "host_server.cc",
         "host_utils.cc",
+        "secure_input.cc",
         "server_common.cc",
         "session.cc",
+        "sign.cc",
         "fonts.S",
     ],
     shared_libs: [
+        "libcn-cbor",
         "libcuttlefish_fs",
         "libbase",
         "libjsoncpp",
         "liblog",
+        "libcrypto",
+        "android.hardware.keymaster@4.0",
     ],
     header_libs: [
         "libcuttlefish_confui_host_headers",
@@ -52,10 +58,11 @@
         "libcuttlefish_host_config",
         "libcuttlefish_utils",
         "libcuttlefish_confui",
+        "libcuttlefish_security",
         "libcuttlefish_wayland_server",
         "libft2.nodep",
         "libteeui",
         "libteeui_localization",
     ],
-    defaults: ["cuttlefish_host"],
+    defaults: ["cuttlefish_buildhost_only"],
 }
diff --git a/host/libs/confui/cbor.cc b/host/libs/confui/cbor.cc
new file mode 100644
index 0000000..e7f667b
--- /dev/null
+++ b/host/libs/confui/cbor.cc
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/confui/cbor.h"
+
+#include "common/libs/confui/confui.h"
+
+namespace cuttlefish {
+namespace confui {
+/**
+ * basically, this creates a map as follows:
+ * {"prompt" : prompt_text_in_UTF8,
+ *  "extra"  : extra_data_in_bytes}
+ */
+void Cbor::Init() {
+  cn_cbor_errback err;
+  cb_map_ = std::unique_ptr<cn_cbor, CborDeleter>(cn_cbor_map_create(&err));
+
+  buffer_status_ = CheckUTF8Copy(prompt_text_);
+  if (!IsOk()) {
+    return;
+  }
+
+  auto cb_prompt_as_value = cn_cbor_string_create(prompt_text_.data(), &err);
+  auto cb_extra_data_as_value =
+      cn_cbor_data_create(extra_data_.data(), extra_data_.size(), &err);
+  cn_cbor_mapput_string(cb_map_.get(), "prompt", cb_prompt_as_value, &err);
+  cn_cbor_mapput_string(cb_map_.get(), "extra", cb_extra_data_as_value, &err);
+
+  // cn_cbor_encoder_write wants buffer_ to have a trailing 0 at the end
+  auto n_chars =
+      cn_cbor_encoder_write(buffer_.data(), 0, buffer_.size(), cb_map_.get());
+  ConfUiLog(ERROR) << "Cn-cbor encoder wrote " << n_chars << " while "
+                   << "kMax is " << kMax;
+  if (n_chars < 0) {
+    // it's either message being too long, or a potential cn_cbor bug
+    ConfUiLog(ERROR) << "Cn-cbor returns -1 which is likely message too long.";
+    buffer_status_ = Error::OUT_OF_DATA;
+  }
+  if (!IsOk()) {
+    return;
+  }
+  buffer_.resize(n_chars);
+}
+
+std::vector<std::uint8_t>&& Cbor::GetMessage() { return std::move(buffer_); }
+
+Cbor::Error Cbor::CheckUTF8Copy(const std::string& text) {
+  auto begin = text.cbegin();
+  auto end = text.cend();
+
+  if (!IsOk()) {
+    return buffer_status_;
+  }
+
+  uint32_t multi_byte_length = 0;
+  Cbor::Error err_code = buffer_status_;  // OK
+
+  while (begin != end) {
+    if (multi_byte_length) {
+      // parsing multi byte character - must start with 10xxxxxx
+      --multi_byte_length;
+      if ((*begin & 0xc0) != 0x80) {
+        return Cbor::Error::MALFORMED_UTF8;
+      }
+    } else if (!((*begin) & 0x80)) {
+      // 7bit character -> nothing to be done
+    } else {
+      // msb is set and we were not parsing a multi byte character
+      // so this must be a header byte
+      char c = *begin << 1;
+      while (c & 0x80) {
+        ++multi_byte_length;
+        c <<= 1;
+      }
+      // headers of the form 10xxxxxx are not allowed
+      if (multi_byte_length < 1) {
+        return Cbor::Error::MALFORMED_UTF8;
+      }
+      // chars longer than 4 bytes are not allowed (multi_byte_length does not
+      // count the header thus > 3
+      if (multi_byte_length > 3) {
+        return Cbor::Error::MALFORMED_UTF8;
+      }
+    }
+    ++begin;
+  }
+  // if the string ends in the middle of a multi byte char it is invalid
+  if (multi_byte_length) {
+    return Cbor::Error::MALFORMED_UTF8;
+  }
+  return err_code;
+}
+}  // end of namespace confui
+}  // end of namespace cuttlefish
diff --git a/host/libs/confui/cbor.h b/host/libs/confui/cbor.h
new file mode 100644
index 0000000..b77e0c7
--- /dev/null
+++ b/host/libs/confui/cbor.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android/hardware/keymaster/4.0/types.h>
+
+#include <cn-cbor/cn-cbor.h>
+
+namespace cuttlefish {
+namespace confui {
+
+/** take prompt_text_, extra_data
+ * returns CBOR map, created with the two
+ *
+ * Usage:
+ *  if (IsOk()) GetMessage()
+ *
+ * The CBOR map is used to create signed confirmation
+ */
+class Cbor {
+  enum class Error : uint32_t {
+    OK = 0,
+    OUT_OF_DATA = 1,
+    MALFORMED = 2,
+    MALFORMED_UTF8 = 3,
+  };
+
+  enum class MessageSize : uint32_t { MAX = 6144u };
+
+  enum class Type : uint8_t {
+    NUMBER = 0,
+    NEGATIVE = 1,
+    BYTE_STRING = 2,
+    TEXT_STRING = 3,
+    ARRAY = 4,
+    MAP = 5,
+    TAG = 6,
+    FLOAT = 7,
+  };
+
+ public:
+  Cbor(const std::string& prompt_text,
+       const std::vector<std::uint8_t>& extra_data)
+      : prompt_text_(prompt_text),
+        extra_data_(extra_data),
+        buffer_status_{Error::OK},
+        buffer_(kMax + 1) {
+    Init();
+  }
+
+  bool IsOk() const { return buffer_status_ == Error::OK; }
+  Error GetErrorCode() const { return buffer_status_; }
+  bool IsMessageTooLong() const { return buffer_status_ == Error::OUT_OF_DATA; }
+  bool IsMalformedUtf8() const {
+    return buffer_status_ == Error::MALFORMED_UTF8;
+  }
+  // call this only when IsOk() returns true
+  std::vector<std::uint8_t>&& GetMessage();
+
+  /** When encoded, the Cbor object should not exceed this limit in terms of
+   * size in bytes
+   */
+  const std::uint32_t kMax = static_cast<std::uint32_t>(MessageSize::MAX);
+
+ private:
+  class CborDeleter {
+   public:
+    void operator()(cn_cbor* ptr) { cn_cbor_free(ptr); }
+  };
+
+  std::unique_ptr<cn_cbor, CborDeleter> cb_map_;
+  std::string prompt_text_;
+  std::vector<std::uint8_t> extra_data_;
+  Error buffer_status_;
+  std::vector<std::uint8_t> buffer_;
+
+  void Init();
+  Error CheckUTF8Copy(const std::string& text);
+};
+
+}  // namespace confui
+}  // end of namespace cuttlefish
diff --git a/host/libs/confui/host_mode_ctrl.h b/host/libs/confui/host_mode_ctrl.h
index 7cf991c..bf8b575 100644
--- a/host/libs/confui/host_mode_ctrl.h
+++ b/host/libs/confui/host_mode_ctrl.h
@@ -30,7 +30,7 @@
  * mechanism to orchestrate concurrent executions of threads
  * that work for screen connector
  *
- * Within VNC or WebRTC service, it tells when it is now in the Android Mode or
+ * Within WebRTC service, it tells when it is now in the Android Mode or
  * Confirmation UI mode
  */
 class HostModeCtrl {
@@ -76,14 +76,14 @@
 
   void SetMode(const ModeType mode) {
     ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
-                     << "tries to acquire the lock in SetMode";
+                     << " tries to acquire the lock in SetMode";
     std::lock_guard<std::mutex> lock(mode_mtx_);
     ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
-                     << "acquired the lock in SetMode";
+                     << " acquired the lock in SetMode";
     atomic_mode_ = mode;
     if (atomic_mode_ == ModeType::kAndroidMode) {
       ConfUiLog(DEBUG) << cuttlefish::confui::thread::GetName()
-                       << "signals kAndroidMode in SetMode";
+                       << " signals kAndroidMode in SetMode";
       and_mode_cv_.notify_all();
       return;
     }
diff --git a/host/libs/confui/host_renderer.cc b/host/libs/confui/host_renderer.cc
index f3e0c52..8bf218b 100644
--- a/host/libs/confui/host_renderer.cc
+++ b/host/libs/confui/host_renderer.cc
@@ -16,6 +16,8 @@
 
 #include "host/libs/confui/host_renderer.h"
 
+#include "host/libs/config/cuttlefish_config.h"
+
 namespace cuttlefish {
 namespace confui {
 static teeui::Color alfaCombineChannel(std::uint32_t shift, double alfa,
@@ -31,31 +33,70 @@
   return result << shift;
 }
 
-ConfUiRenderer::ConfUiRenderer(const std::uint32_t display)
-    : display_num_(display),
-      lang_id_{"en"},
-      prompt_("Am I Yumi Meow?"),
-      current_height_(ScreenConnectorInfo::ScreenHeight(display_num_)),
-      current_width_(ScreenConnectorInfo::ScreenWidth(display_num_)),
-      color_bg_{kColorBackground},
-      color_text_{kColorDisabled},
-      shield_color_{kColorShield},
-      is_inverted_{false},
-      ctx_{GetDeviceContext()} {
-  auto opted_frame = RepaintRawFrame(prompt_, lang_id_);
-  if (opted_frame) {
-    raw_frame_ = std::move(opted_frame.value());
+std::unique_ptr<ConfUiRenderer> ConfUiRenderer::GenerateRenderer(
+    const std::uint32_t display, const std::string& confirmation_msg,
+    const std::string& locale, const bool inverted, const bool magnified) {
+  ConfUiRenderer* raw_ptr = new ConfUiRenderer(display, confirmation_msg,
+                                               locale, inverted, magnified);
+  if (raw_ptr && raw_ptr->IsSetUpSuccessful()) {
+    return std::unique_ptr<ConfUiRenderer>(raw_ptr);
   }
+  return nullptr;
 }
 
-void ConfUiRenderer::SetConfUiMessage(const std::string& msg) {
-  prompt_ = msg;
-  SetText<LabelConfMsg>(msg);
+static int GetDpi(const int display_num = 0) {
+  auto config = CuttlefishConfig::Get();
+  CHECK(config) << "Config is Missing";
+  auto display_configs = config->display_configs();
+  CHECK_GT(display_configs.size(), display_num)
+      << "Invalid display number " << display_num;
+  return display_configs[display_num].dpi;
 }
 
-teeui::Error ConfUiRenderer::SetLangId(const std::string& lang_id) {
+/**
+ * device configuration
+ *
+ * ctx_{# of pixels in 1 mm, # of pixels per 1 density independent pixels}
+ *
+ * The numbers are, however, to fit for the host webRTC local/remote clients
+ * in general, not necessarily the supposedly guest device (e.g. Auto, phone,
+ * etc)
+ *
+ * In general, for a normal PC, roughly ctx_(6.45211, 400.0/412.0) is a good
+ * combination for the default DPI, 320. If we want to see the impact
+ * of the change in the guest DPI, we could adjust the combination above
+ * proportionally
+ *
+ */
+ConfUiRenderer::ConfUiRenderer(const std::uint32_t display,
+                               const std::string& confirmation_msg,
+                               const std::string& locale, const bool inverted,
+                               const bool magnified)
+    : display_num_{display},
+      lang_id_{locale},
+      prompt_text_{confirmation_msg},
+      current_height_{ScreenConnectorInfo::ScreenHeight(display_num_)},
+      current_width_{ScreenConnectorInfo::ScreenWidth(display_num_)},
+      is_inverted_(inverted),
+      is_magnified_(magnified),
+      ctx_(6.45211 * GetDpi() / 320.0, 400.0 / 412.0 * GetDpi() / 320.0),
+      is_setup_well_(false) {
+  SetDeviceContext(current_width_, current_height_, is_inverted_,
+                   is_magnified_);
+  layout_ = teeui::instantiateLayout(teeui::ConfUILayout(), ctx_);
+
+  if (auto error = UpdateLocale()) {
+    ConfUiLog(ERROR) << "Update Translation Error: " << Enum2Base(error.code());
+    // is_setup_well_ = false;
+    return;
+  }
+  UpdateColorScheme(is_inverted_);
+  SetText<LabelConfMsg>(prompt_text_);
+  is_setup_well_ = true;
+}
+
+teeui::Error ConfUiRenderer::UpdateLocale() {
   using teeui::Error;
-  lang_id_ = lang_id;
   teeui::localization::selectLangId(lang_id_.c_str());
   if (auto error = UpdateTranslations()) {
     return error;
@@ -63,14 +104,25 @@
   return Error::OK;
 }
 
+template <typename Label>
+teeui::Error ConfUiRenderer::UpdateString() {
+  using namespace teeui;
+  const char* str;
+  auto& label = std::get<Label>(layout_);
+  str = localization::lookup(TranslationId(label.textId()));
+  if (str == nullptr) {
+    ConfUiLog(ERROR) << "Given translation_id" << label.textId() << "not found";
+    return Error::Localization;
+  }
+  label.setText({str, str + strlen(str)});
+  return Error::OK;
+}
+
 teeui::Error ConfUiRenderer::UpdateTranslations() {
   using namespace teeui;
   if (auto error = UpdateString<LabelOK>()) {
     return error;
   }
-  if (auto error = UpdateString<LabelOK>()) {
-    return error;
-  }
   if (auto error = UpdateString<LabelCancel>()) {
     return error;
   }
@@ -83,41 +135,41 @@
   return Error::OK;
 }
 
-teeui::context<teeui::ConUIParameters> ConfUiRenderer::GetDeviceContext() {
+void ConfUiRenderer::SetDeviceContext(const unsigned long long w,
+                                      const unsigned long long h,
+                                      const bool is_inverted,
+                                      const bool is_magnified) {
   using namespace teeui;
-  const unsigned long long w = ScreenConnectorInfo::ScreenWidth(display_num_);
-  const unsigned long long h = ScreenConnectorInfo::ScreenHeight(display_num_);
   const auto screen_width = operator""_px(w);
   const auto screen_height = operator""_px(h);
-  context<teeui::ConUIParameters> ctx(6.45211, 400.0 / 412.0);
-  ctx.setParam<RightEdgeOfScreen>(screen_width);
-  ctx.setParam<BottomOfScreen>(screen_height);
-  ctx.setParam<PowerButtonTop>(20.26_mm);
-  ctx.setParam<PowerButtonBottom>(30.26_mm);
-  ctx.setParam<VolUpButtonTop>(40.26_mm);
-  ctx.setParam<VolUpButtonBottom>(50.26_mm);
-  ctx.setParam<DefaultFontSize>(14_dp);
-  ctx.setParam<BodyFontSize>(16_dp);
-  return ctx;
-}
-
-bool ConfUiRenderer::InitLayout(const std::string& lang_id) {
-  layout_ = teeui::instantiateLayout(teeui::ConfUILayout(), ctx_);
-  SetLangId(lang_id);
-  if (auto error = UpdateTranslations()) {
-    ConfUiLog(ERROR) << "Update Translation Error";
-    return false;
+  ctx_.setParam<RightEdgeOfScreen>(pxs(screen_width));
+  ctx_.setParam<BottomOfScreen>(pxs(screen_height));
+  if (is_magnified) {
+    ctx_.setParam<DefaultFontSize>(18_dp);
+    ctx_.setParam<BodyFontSize>(20_dp);
+  } else {
+    ctx_.setParam<DefaultFontSize>(14_dp);
+    ctx_.setParam<BodyFontSize>(16_dp);
   }
-  UpdateColorScheme(&ctx_);
-  return true;
+  if (is_inverted) {
+    ctx_.setParam<ShieldColor>(kColorShieldInv);
+    ctx_.setParam<ColorText>(kColorTextInv);
+    ctx_.setParam<ColorBG>(kColorBackgroundInv);
+    ctx_.setParam<ColorButton>(kColorShieldInv);
+  } else {
+    ctx_.setParam<ShieldColor>(kColorShield);
+    ctx_.setParam<ColorText>(kColorText);
+    ctx_.setParam<ColorBG>(kColorBackground);
+    ctx_.setParam<ColorButton>(kColorShield);
+  }
 }
 
-teeui::Error ConfUiRenderer::UpdatePixels(TeeUiFrame& raw_frame,
+teeui::Error ConfUiRenderer::UpdatePixels(TeeUiFrameWrapper& raw_frame,
                                           std::uint32_t x, std::uint32_t y,
                                           teeui::Color color) {
   auto buffer = raw_frame.data();
-  const auto height = ScreenConnectorInfo::ScreenHeight(display_num_);
-  const auto width = ScreenConnectorInfo::ScreenWidth(display_num_);
+  const auto height = raw_frame.Height();
+  const auto width = raw_frame.Width();
   auto pos = width * y + x;
   if (pos >= (height * width)) {
     ConfUiLog(ERROR) << "Rendering Out of Bound";
@@ -131,114 +183,69 @@
   return teeui::Error::OK;
 }
 
-std::tuple<TeeUiFrame&, bool> ConfUiRenderer::RenderRawFrame(
-    const std::string& confirmation_msg, const std::string& lang_id) {
+void ConfUiRenderer::UpdateColorScheme(const bool is_inverted) {
+  using namespace teeui;
+  color_text_ = is_inverted ? kColorDisabledInv : kColorDisabled;
+  shield_color_ = is_inverted ? kColorShieldInv : kColorShield;
+  color_bg_ = is_inverted ? kColorBackgroundInv : kColorBackground;
+
+  ctx_.setParam<ShieldColor>(shield_color_);
+  ctx_.setParam<ColorText>(color_text_);
+  ctx_.setParam<ColorBG>(color_bg_);
+  return;
+}
+
+std::shared_ptr<TeeUiFrameWrapper> ConfUiRenderer::RenderRawFrame() {
   /* we repaint only if one or more of the followng meet:
    *
    *  1. raw_frame_ is empty
    *  2. the current_width_ and current_height_ is out of date
-   *  3. lang_id is different (e.g. new locale)
    *
-   *  in the future, maybe you wanna inverted, new background, etc?
    */
-  if (lang_id != lang_id_ || !IsFrameReady() ||
-      current_height_ != ScreenConnectorInfo::ScreenHeight(display_num_) ||
-      current_width_ != ScreenConnectorInfo::ScreenWidth(display_num_)) {
-    auto opted_new_frame = RepaintRawFrame(confirmation_msg, lang_id_);
-    if (opted_new_frame) {
-      // repainting from the scratch successful in a new frame
-      raw_frame_ = std::move(opted_new_frame.value());
-      return {raw_frame_, true};
+  const int w = ScreenConnectorInfo::ScreenWidth(display_num_);
+  const int h = ScreenConnectorInfo::ScreenHeight(display_num_);
+  if (!IsFrameReady() || current_height_ != h || current_width_ != w) {
+    auto new_frame = RepaintRawFrame(w, h);
+    if (!new_frame) {
+      // must repaint but failed
+      raw_frame_ = nullptr;
+      return nullptr;
     }
-    // repaint failed even if it was necessary, so returns invalid values
-    raw_frame_.clear();
-    return {raw_frame_, false};
+    // repainting from the scratch successful in a new frame
+    raw_frame_ = std::move(new_frame);
+    current_width_ = w;
+    current_height_ = h;
   }
-  // no need to repaint from the scratch. repaint the confirmation message only
-  // the frame is mostly already in raw_frame_
-  auto ret_code = RenderConfirmationMsgOnly(confirmation_msg);
-  return {raw_frame_, (ret_code == teeui::Error::OK)};
+  return raw_frame_;
 }
 
-std::optional<TeeUiFrame> ConfUiRenderer::RepaintRawFrame(
-    const std::string& confirmation_msg, const std::string& lang_id) {
-  /*
-   * NOTE: DON'T use current_width_/height_ to create this frame
-   * it may fail, and then we must not mess up the current_width_, height_
-   *
-   */
-  if (!InitLayout(lang_id)) {
-    return std::nullopt;
-  }
-  SetConfUiMessage(confirmation_msg);
-  auto color = kColorEnabled;
-  std::get<teeui::LabelOK>(layout_).setTextColor(color);
-  std::get<teeui::LabelCancel>(layout_).setTextColor(color);
+std::unique_ptr<TeeUiFrameWrapper> ConfUiRenderer::RepaintRawFrame(
+    const int w, const int h) {
+  std::get<teeui::LabelOK>(layout_).setTextColor(kColorEnabled);
+  std::get<teeui::LabelCancel>(layout_).setTextColor(kColorEnabled);
 
-  /* in the future, if ever we need to register a handler for the
-     Label{OK,Cancel}. do this: std::get<teeui::LabelOK>(layout_)
-     .setCB(teeui::makeCallback<teeui::Error, teeui::Event>(
-     [](teeui::Event e, void* p) -> teeui::Error {
-     LOG(DEBUG) << "Calling callback for Confirm?";
-     return reinterpret_cast<decltype(owner)*>(p)->TapOk(e); },
-     owner));
-  */
-  // we manually check if click happened, where if yes, and generate the label
-  // event manually. So we won't register the handler here.
   /**
    * should be uint32_t for teeui APIs.
    * It assumes that each raw frame buffer element is 4 bytes
    */
-  TeeUiFrame new_raw_frame(
-      ScreenConnectorInfo::ScreenSizeInBytes(display_num_) / 4,
-      kColorBackground);
-
+  const teeui::Color background_color =
+      is_inverted_ ? kColorBackgroundInv : kColorBackground;
+  auto new_raw_frame =
+      std::make_unique<TeeUiFrameWrapper>(w, h, background_color);
   auto draw_pixel = teeui::makePixelDrawer(
       [this, &new_raw_frame](std::uint32_t x, std::uint32_t y,
                              teeui::Color color) -> teeui::Error {
-        return this->UpdatePixels(new_raw_frame, x, y, color);
+        return this->UpdatePixels(*new_raw_frame, x, y, color);
       });
 
   // render all components
   const auto error = drawElements(layout_, draw_pixel);
   if (error) {
     ConfUiLog(ERROR) << "Painting failed: " << error.code();
-    return std::nullopt;
+    return nullptr;
   }
 
-  // set current frame's dimension as frame generation was successful
-  current_height_ = ScreenConnectorInfo::ScreenHeight(display_num_);
-  current_width_ = ScreenConnectorInfo::ScreenWidth(display_num_);
-
-  return {new_raw_frame};
-}
-
-teeui::Error ConfUiRenderer::RenderConfirmationMsgOnly(
-    const std::string& confirmation_msg) {
-  // repaint labelbody on the raw_frame__ only
-  auto callback_func = [this](std::uint32_t x, std::uint32_t y,
-                              teeui::Color color) -> teeui::Error {
-    return UpdatePixels(raw_frame_, x, y, color);
-  };
-  auto draw_pixel = teeui::makePixelDrawer(callback_func);
-  LabelConfMsg& label = std::get<LabelConfMsg>(layout_);
-  auto b = GetBoundary(label);
-  for (std::uint32_t i = 0; i != b.w; i++) {
-    const auto col_index = i + b.x - 1;
-    for (std::uint32_t j = 0; j != b.y; j++) {
-      const auto row_index = (j + b.y - 1);
-      raw_frame_[current_width_ * row_index + col_index] = color_bg_;
-    }
-  }
-
-  SetConfUiMessage(confirmation_msg);
-  ConfUiLog(DEBUG) << "Repaint Confirmation Msg with :" << prompt_;
-  if (auto error = std::get<LabelConfMsg>(layout_).draw(draw_pixel)) {
-    ConfUiLog(ERROR) << "Repainting Confirmation Message Label failed:"
-                     << error.code();
-    return error;
-  }
-  return teeui::Error::OK;
+  return new_raw_frame;
 }
 
 }  // end of namespace confui
diff --git a/host/libs/confui/host_renderer.h b/host/libs/confui/host_renderer.h
index c614109..6dac3e3 100644
--- a/host/libs/confui/host_renderer.h
+++ b/host/libs/confui/host_renderer.h
@@ -29,45 +29,78 @@
 #include "common/libs/confui/confui.h"
 #include "host/libs/confui/layouts/layout.h"
 #include "host/libs/confui/server_common.h"
-#include "host/libs/screen_connector/screen_connector.h"
+#include "host/libs/screen_connector/screen_connector_common.h"
 
 namespace cuttlefish {
 namespace confui {
+class TeeUiFrameWrapper {
+ public:
+  TeeUiFrameWrapper(const int w, const int h, const teeui::Color color)
+      : w_(w), h_(h), teeui_frame_(ScreenSizeInBytes(w, h), color) {}
+  TeeUiFrameWrapper() = delete;
+  auto data() { return teeui_frame_.data(); }
+  int Width() const { return w_; }
+  int Height() const { return h_; }
+  bool IsEmpty() const { return teeui_frame_.empty(); }
+  auto Size() const { return teeui_frame_.size(); }
+  auto& operator[](const int idx) { return teeui_frame_[idx]; }
+  std::uint32_t ScreenStrideBytes() const {
+    return ScreenConnectorInfo::ComputeScreenStrideBytes(w_);
+  }
+
+ private:
+  static std::uint32_t ScreenSizeInBytes(const int w, const int h) {
+    return ScreenConnectorInfo::ComputeScreenSizeInBytes(w, h);
+  }
+
+  int w_;
+  int h_;
+  TeeUiFrame teeui_frame_;
+};
 
 /**
  * create a raw frame for confirmation UI dialog
+ *
+ * Many rendering code borrowed from the following source
+ *  https://android.googlesource.com/trusty/app/confirmationui/+/0429cc7/src
  */
 class ConfUiRenderer {
  public:
   using LabelConfMsg = teeui::LabelBody;
 
-  ConfUiRenderer(const std::uint32_t display);
+  static std::unique_ptr<ConfUiRenderer> GenerateRenderer(
+      const std::uint32_t display, const std::string& confirmation_msg,
+      const std::string& locale, const bool inverted, const bool magnified);
 
   /**
    * this does not repaint from the scratch all the time
    *
-   * Unless repainting the whole thing is needed, it remove the message
-   * label, and re-draw there. There seems yet no fancy way of doing this.
-   * Thus, it repaint the background color on the top of the label, and
-   * draw the label on the new background
-   *
-   * As HostRenderer is intended to be shared across sessions, HostRender
-   * owns the buffer, and returns reference to the buffer. Note that no
-   * 2 or more sessions are concurrently executed. Only 1 or 0 is active
-   * at the given moment.
+   * It does repaint its frame buffer only when w/h of
+   * current display has changed
    */
-  std::tuple<TeeUiFrame&, bool> RenderRawFrame(
-      const std::string& confirmation_msg, const std::string& lang_id = "en");
+  std::shared_ptr<TeeUiFrameWrapper> RenderRawFrame();
 
-  bool IsFrameReady() const { return !raw_frame_.empty(); }
+  bool IsFrameReady() const { return raw_frame_ && !raw_frame_->IsEmpty(); }
+
+  bool IsInConfirm(const std::uint32_t x, const std::uint32_t y) {
+    return IsInside<teeui::LabelOK>(x, y);
+  }
+  bool IsInCancel(const std::uint32_t x, const std::uint32_t y) {
+    return IsInside<teeui::LabelCancel>(x, y);
+  }
 
  private:
+  bool IsSetUpSuccessful() const { return is_setup_well_; }
+  ConfUiRenderer(const std::uint32_t display,
+                 const std::string& confirmation_msg, const std::string& locale,
+                 const bool inverted, const bool magnified);
+
   struct Boundary {            // inclusive but.. LayoutElement's size is float
     std::uint32_t x, y, w, h;  // (x, y) is the top left
   };
 
   template <typename LayoutElement>
-  Boundary GetBoundary(LayoutElement&& e) {
+  Boundary GetBoundary(LayoutElement&& e) const {
     auto box = e.bounds_;
     Boundary b;
     // (x,y) is left top. so floor() makes sense
@@ -80,28 +113,28 @@
     return b;
   }
 
+  template <typename Element>
+  bool IsInside(const std::uint32_t x, const std::uint32_t y) const {
+    auto box = GetBoundary(std::get<Element>(layout_));
+    if (x >= box.x && x <= box.x + box.w && y >= box.y && y <= box.y + box.h) {
+      return true;
+    }
+    return false;
+  }
   // essentially, to repaint from the scratch, so returns new frame
   // when successful. Or, nullopt
-  std::optional<TeeUiFrame> RepaintRawFrame(const std::string& confirmation_msg,
-                                            const std::string& lang_id = "en");
+  std::unique_ptr<TeeUiFrameWrapper> RepaintRawFrame(const int w, const int h);
 
   bool InitLayout(const std::string& lang_id);
   teeui::Error UpdateTranslations();
-  /**
-   * could be confusing. update prompt_, and update the text_ in the Label
-   * object, the GUI components. This does not render immediately. And..
-   * to render it, we must clean up the existing dirty pixels, which
-   * this method does not do.
-   */
-  void SetConfUiMessage(const std::string& s);
-  teeui::Error SetLangId(const std::string& lang_id);
-  teeui::context<teeui::ConUIParameters> GetDeviceContext();
+  teeui::Error UpdateLocale();
+  void SetDeviceContext(const unsigned long long w, const unsigned long long h,
+                        bool is_inverted, bool is_magnified);
 
-  // effectively, will be send to teeui as a callback function
-  teeui::Error UpdatePixels(TeeUiFrame& buffer, std::uint32_t x,
+  // a callback function to be effectively sent to TeeUI library
+  teeui::Error UpdatePixels(TeeUiFrameWrapper& buffer, std::uint32_t x,
                             std::uint32_t y, teeui::Color color);
 
-  // from Trusty
   // second param is for type deduction
   template <typename... Elements>
   static teeui::Error drawElements(std::tuple<Elements...>& layout,
@@ -111,70 +144,49 @@
     // draw the remaining elements in the order they appear in the layout tuple.
     return (std::get<Elements>(layout).draw(drawPixel) || ...);
   }
-
-  // repaint the confirmation UI label only
-  teeui::Error RenderConfirmationMsgOnly(const std::string& confirmation_msg);
-
-  // from Trusty
-  template <typename Context>
-  void UpdateColorScheme(Context* ctx) {
-    using namespace teeui;
-    color_text_ = is_inverted_ ? kColorDisabledInv : kColorDisabled;
-    shield_color_ = is_inverted_ ? kColorShieldInv : kColorShield;
-    color_bg_ = is_inverted_ ? kColorBackgroundInv : kColorBackground;
-
-    ctx->template setParam<ShieldColor>(shield_color_);
-    ctx->template setParam<ColorText>(color_text_);
-    ctx->template setParam<ColorBG>(color_bg_);
-    return;
-  }
-
+  void UpdateColorScheme(const bool is_inverted);
   template <typename Label>
   auto SetText(const std::string& text) {
     return std::get<Label>(layout_).setText(
         {text.c_str(), text.c_str() + text.size()});
   }
 
-  /**
-   * source:
-   * https://android.googlesource.com/trusty/app/confirmationui/+/0429cc7/src/trusty_confirmation_ui.cpp#49
-   */
   template <typename Label>
-  teeui::Error UpdateString() {
-    using namespace teeui;
-    const char* str;
-    auto& label = std::get<Label>(layout_);
-    str = localization::lookup(TranslationId(label.textId()));
-    if (str == nullptr) {
-      ConfUiLog(ERROR) << "Given translation_id" << label.textId()
-                       << "not found";
-      return Error::Localization;
-    }
-    label.setText({str, str + strlen(str)});
-    return Error::OK;
-  }
+  teeui::Error UpdateString();
 
-  const int display_num_;
+  std::uint32_t display_num_;
   teeui::layout_t<teeui::ConfUILayout> layout_;
   std::string lang_id_;
-  std::string prompt_;  // confirmation ui message
-  TeeUiFrame raw_frame_;
+  std::string prompt_text_;  // confirmation ui message
+
+  /**
+   * Potentially, the same frame could be requested multiple times.
+   *
+   * While another thread/caller is using this frame, the frame should
+   * be kept here, too, to be returned upon future requests.
+   *
+   */
+  std::shared_ptr<TeeUiFrameWrapper> raw_frame_;
   std::uint32_t current_height_;
   std::uint32_t current_width_;
   teeui::Color color_bg_;
   teeui::Color color_text_;
   teeui::Color shield_color_;
   bool is_inverted_;
-  teeui::context<teeui::ConUIParameters> ctx_;
+  bool is_magnified_;
+  teeui::context<teeui::ConfUIParameters> ctx_;
+  bool is_setup_well_;
 
-  static constexpr const teeui::Color kColorEnabled = 0xff212121;
-  static constexpr const teeui::Color kColorDisabled = 0xffbdbdbd;
-  static constexpr const teeui::Color kColorEnabledInv = 0xffdedede;
-  static constexpr const teeui::Color kColorDisabledInv = 0xff424242;
   static constexpr const teeui::Color kColorBackground = 0xffffffff;
   static constexpr const teeui::Color kColorBackgroundInv = 0xff212121;
-  static constexpr const teeui::Color kColorShieldInv = 0xffc4cb80;
+  static constexpr const teeui::Color kColorDisabled = 0xffbdbdbd;
+  static constexpr const teeui::Color kColorDisabledInv = 0xff424242;
+  static constexpr const teeui::Color kColorEnabled = 0xff212121;
+  static constexpr const teeui::Color kColorEnabledInv = 0xffdedede;
   static constexpr const teeui::Color kColorShield = 0xff778500;
+  static constexpr const teeui::Color kColorShieldInv = 0xffc4cb80;
+  static constexpr const teeui::Color kColorText = 0xff212121;
+  static constexpr const teeui::Color kColorTextInv = 0xffdedede;
 };
 }  // end of namespace confui
 }  // end of namespace cuttlefish
diff --git a/host/libs/confui/host_server.cc b/host/libs/confui/host_server.cc
index c14e0ed..b510759 100644
--- a/host/libs/confui/host_server.cc
+++ b/host/libs/confui/host_server.cc
@@ -22,8 +22,10 @@
 #include <tuple>
 
 #include "common/libs/confui/confui.h"
+#include "common/libs/fs/shared_buf.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/confui/host_utils.h"
+#include "host/libs/confui/secure_input.h"
 
 namespace cuttlefish {
 namespace confui {
@@ -33,8 +35,31 @@
   return config->ForDefaultInstance();
 }
 
-static std::string HalGuestSocketPath() {
-  return CuttlefishConfigDefaultInstance().confui_hal_guest_socket_path();
+static int HalHostVsockPort() {
+  return CuttlefishConfigDefaultInstance().confui_host_vsock_port();
+}
+
+/**
+ * null if not user/touch, or wrap it and ConfUiSecure{Selection,Touch}Message
+ *
+ * ConfUiMessage must NOT ConfUiSecure{Selection,Touch}Message types
+ */
+static std::unique_ptr<ConfUiMessage> WrapWithSecureFlag(
+    const ConfUiMessage& base_msg, const bool secure) {
+  switch (base_msg.GetType()) {
+    case ConfUiCmd::kUserInputEvent: {
+      const ConfUiUserSelectionMessage& as_selection =
+          static_cast<const ConfUiUserSelectionMessage&>(base_msg);
+      return ToSecureSelectionMessage(as_selection, secure);
+    }
+    case ConfUiCmd::kUserTouchEvent: {
+      const ConfUiUserTouchMessage& as_touch =
+          static_cast<const ConfUiUserTouchMessage&>(base_msg);
+      return ToSecureTouchMessage(as_touch, secure);
+    }
+    default:
+      return nullptr;
+  }
 }
 
 HostServer& HostServer::Get(
@@ -50,16 +75,24 @@
     : display_num_(0),
       host_mode_ctrl_(host_mode_ctrl),
       screen_connector_{screen_connector},
-      renderer_(display_num_),
-      hal_socket_path_(HalGuestSocketPath()),
-      input_multiplexer_{/* max n_elems */ 20, /* n_Qs */ 2} {
-  hal_cmd_q_id_ = input_multiplexer_.GetNewQueueId();         // return 0
-  user_input_evt_q_id_ = input_multiplexer_.GetNewQueueId();  // return 1
+      hal_vsock_port_(HalHostVsockPort()) {
+  ConfUiLog(DEBUG) << "Confirmation UI Host session is listening on: "
+                   << hal_vsock_port_;
+  const size_t max_elements = 20;
+  auto ignore_new =
+      [](ThreadSafeQueue<std::unique_ptr<ConfUiMessage>>::QueueImpl*) {
+        // no op, so the queue is still full, and the new item will be discarded
+        return;
+      };
+  hal_cmd_q_id_ = input_multiplexer_.RegisterQueue(
+      HostServer::Multiplexer::CreateQueue(max_elements, ignore_new));
+  user_input_evt_q_id_ = input_multiplexer_.RegisterQueue(
+      HostServer::Multiplexer::CreateQueue(max_elements, ignore_new));
 }
 
 void HostServer::Start() {
-  guest_hal_socket_ = cuttlefish::SharedFD::SocketLocalServer(
-      hal_socket_path_, false, SOCK_STREAM, 0666);
+  guest_hal_socket_ =
+      cuttlefish::SharedFD::VsockServer(hal_vsock_port_, SOCK_STREAM);
   if (!guest_hal_socket_->IsOpen()) {
     ConfUiLog(FATAL) << "Confirmation UI host service mandates a server socket"
                      << "to which the guest HAL to connect.";
@@ -70,67 +103,74 @@
   hal_input_fetcher_thread_ =
       thread::RunThread("HalInputLoop", hal_cmd_fetching);
   main_loop_thread_ = thread::RunThread("MainLoop", main);
-  ConfUiLog(DEBUG) << "configured internal socket based input.";
+  ConfUiLog(DEBUG) << "configured internal vsock based input.";
   return;
 }
 
 void HostServer::HalCmdFetcherLoop() {
-  hal_cli_socket_ = EstablishHalConnection();
-  if (!hal_cli_socket_->IsOpen()) {
-    ConfUiLog(FATAL)
-        << "Confirmation UI host service mandates connection with HAL.";
-    return;
-  }
   while (true) {
-    auto opted_msg = RecvConfUiMsg(hal_cli_socket_);
-    if (!opted_msg) {
-      ConfUiLog(ERROR) << "Error in RecvConfUiMsg from HAL";
+    if (!hal_cli_socket_->IsOpen()) {
+      ConfUiLog(DEBUG) << "client is disconnected";
+      std::unique_lock<std::mutex> lk(socket_flag_mtx_);
+      hal_cli_socket_ = EstablishHalConnection();
+      is_socket_ok_ = true;
       continue;
     }
-    auto input = std::move(opted_msg.value());
-    input_multiplexer_.Push(hal_cmd_q_id_, std::move(input));
+    auto msg = RecvConfUiMsg(hal_cli_socket_);
+    if (!msg) {
+      ConfUiLog(ERROR) << "Error in RecvConfUiMsg from HAL";
+      hal_cli_socket_->Close();
+      is_socket_ok_ = false;
+      continue;
+    }
+    /*
+     * In case of Vts test, the msg could be a user input. For now, we do not
+     * enforce the input grace period for Vts. However, if ever we do, here is
+     * where the time point check should happen. Once it is enqueued, it is not
+     * always guaranteed to be picked up reasonably soon.
+     */
+    constexpr bool is_secure = false;
+    auto to_override_if_user_input = WrapWithSecureFlag(*msg, is_secure);
+    if (to_override_if_user_input) {
+      msg = std::move(to_override_if_user_input);
+    }
+    input_multiplexer_.Push(hal_cmd_q_id_, std::move(msg));
   }
 }
 
-bool HostServer::SendUserSelection(UserResponse::type selection) {
-  if (!curr_session_) {
-    ConfUiLog(FATAL) << "Current session must not be null";
-    return false;
-  }
-  if (curr_session_->GetState() != MainLoopState::kInSession) {
+void HostServer::SendUserSelection(std::unique_ptr<ConfUiMessage>& input) {
+  if (!curr_session_ ||
+      curr_session_->GetState() != MainLoopState::kInSession ||
+      !curr_session_->IsReadyForUserInput()) {
     // ignore
-    return true;
-  }
-
-  std::lock_guard<std::mutex> lock(input_socket_mtx_);
-  if (selection != UserResponse::kConfirm &&
-      selection != UserResponse::kCancel) {
-    ConfUiLog(FATAL) << selection << " must be either" << UserResponse::kConfirm
-                     << "or" << UserResponse::kCancel;
-    return false;  // not reaching here
-  }
-
-  ConfUiMessage input{GetCurrentSessionId(),
-                      ToString(ConfUiCmd::kUserInputEvent), selection};
-
-  input_multiplexer_.Push(user_input_evt_q_id_, std::move(input));
-  return true;
-}
-
-void HostServer::PressConfirmButton(const bool is_down) {
-  if (!is_down) {
     return;
   }
-  // shared by N vnc/webRTC clients
-  SendUserSelection(UserResponse::kConfirm);
+  constexpr bool is_secure = true;
+  auto secure_input = WrapWithSecureFlag(*input, is_secure);
+  input_multiplexer_.Push(user_input_evt_q_id_, std::move(secure_input));
 }
 
-void HostServer::PressCancelButton(const bool is_down) {
-  if (!is_down) {
+void HostServer::TouchEvent(const int x, const int y, const bool is_down) {
+  if (!is_down || !curr_session_) {
     return;
   }
-  // shared by N vnc/webRTC clients
-  SendUserSelection(UserResponse::kCancel);
+  std::unique_ptr<ConfUiMessage> input =
+      std::make_unique<ConfUiUserTouchMessage>(GetCurrentSessionId(), x, y);
+  constexpr bool is_secure = true;
+  auto secure_input = WrapWithSecureFlag(*input, is_secure);
+  SendUserSelection(secure_input);
+}
+
+void HostServer::UserAbortEvent() {
+  if (!curr_session_) {
+    return;
+  }
+  std::unique_ptr<ConfUiMessage> input =
+      std::make_unique<ConfUiUserSelectionMessage>(GetCurrentSessionId(),
+                                                   UserResponse::kUserAbort);
+  constexpr bool is_secure = true;
+  auto secure_input = WrapWithSecureFlag(*input, is_secure);
+  SendUserSelection(secure_input);
 }
 
 bool HostServer::IsConfUiActive() {
@@ -141,119 +181,105 @@
 }
 
 SharedFD HostServer::EstablishHalConnection() {
-  ConfUiLog(DEBUG) << "Waiting hal accepting";
-  auto new_cli = SharedFD::Accept(*guest_hal_socket_);
-  ConfUiLog(DEBUG) << "hal client accepted";
-  return new_cli;
-}
-
-std::unique_ptr<Session> HostServer::ComputeCurrentSession(
-    const std::string& session_id) {
-  if (curr_session_ && (GetCurrentSessionId() != session_id)) {
-    ConfUiLog(FATAL) << curr_session_->GetId() << " is active and in the"
-                     << GetCurrentState() << "but HAL sends command to"
-                     << session_id;
+  using namespace std::chrono_literals;
+  while (true) {
+    ConfUiLog(VERBOSE) << "Waiting hal accepting";
+    auto new_cli = SharedFD::Accept(*guest_hal_socket_);
+    ConfUiLog(VERBOSE) << "hal client accepted";
+    if (new_cli->IsOpen()) {
+      return new_cli;
+    }
+    std::this_thread::sleep_for(500ms);
   }
-  if (curr_session_) {
-    return std::move(curr_session_);
-  }
-
-  // pick up a new session, or create one
-  auto result = GetSession(session_id);
-  if (result) {
-    return std::move(result);
-  }
-
-  auto raw_ptr = new Session(session_id, display_num_, renderer_,
-                             host_mode_ctrl_, screen_connector_);
-  result = std::unique_ptr<Session>(raw_ptr);
-  // note that the new session is directly going to curr_session_
-  // when it is suspended, it will be moved to session_map_
-  return std::move(result);
 }
 
 // read the comments in the header file
 [[noreturn]] void HostServer::MainLoop() {
   while (true) {
     // this gets one input from either queue:
-    // from HAL or from all webrtc/vnc clients
+    // from HAL or from all webrtc clients
     // if no input, sleep until there is
-    const auto input = input_multiplexer_.Pop();
-    const auto& [session_id, cmd_str, additional_info] = input;
+    auto input_ptr = input_multiplexer_.Pop();
+    auto& input = *input_ptr;
+    const auto session_id = input.GetSessionId();
+    const auto cmd = input.GetType();
+    const std::string cmd_str(ToString(cmd));
 
     // take input for the Finite States Machine below
-    const ConfUiCmd cmd = ToCmd(cmd_str);
-    const bool is_user_input = (cmd == ConfUiCmd::kUserInputEvent);
-    std::string src = is_user_input ? "input" : "hal";
+    std::string src = input.IsUserInput() ? "input" : "hal";
+    ConfUiLog(VERBOSE) << "In Session " << GetCurrentSessionId() << ", "
+                       << "in state " << GetCurrentState() << ", "
+                       << "received input from " << src << " cmd =" << cmd_str
+                       << " going to session " << session_id;
 
-    ConfUiLog(DEBUG) << "In Session" << GetCurrentSessionId() << ","
-                     << "in state" << GetCurrentState() << ","
-                     << "received input from" << src << "cmd =" << cmd_str
-                     << "and additional_info =" << additional_info
-                     << "going to session" << session_id;
-
-    FsmInput fsm_input = ToFsmInput(input);
-
-    if (is_user_input && !curr_session_) {
-      // discard the input, there's no session to take it yet
-      // actually, no confirmation UI screen is available
-      ConfUiLog(DEBUG) << "Took user input but no active session is available.";
-      continue;
-    }
-
-    /**
-     * if the curr_session_ is null, create one
-     * if the curr_session_ is not null but the session id doesn't match,
-     * something is wrong. Confirmation UI doesn't allow preemption by
-     * another confirmation UI session back to back. When it's preempted,
-     * HAL must send "kSuspend"
-     *
-     */
-    curr_session_ = ComputeCurrentSession(session_id);
-    ConfUiLog(DEBUG) << "Host service picked up "
-                     << (curr_session_ ? curr_session_->GetId()
-                                       : "null session");
-    ConfUiLog(DEBUG) << "The state of current session is "
-                     << (curr_session_ ? ToString(curr_session_->GetState())
-                                       : "null session");
-
-    if (is_user_input) {
-      curr_session_->Transition(is_user_input, hal_cli_socket_, fsm_input,
-                                additional_info);
-    } else {
-      ConfUiCmd cmd = ToCmd(cmd_str);
-      switch (cmd) {
-        case ConfUiCmd::kSuspend:
-          curr_session_->Suspend(hal_cli_socket_);
-          break;
-        case ConfUiCmd::kRestore:
-          curr_session_->Restore(hal_cli_socket_);
-          break;
-        case ConfUiCmd::kAbort:
-          curr_session_->Abort(hal_cli_socket_);
-          break;
-        default:
-          curr_session_->Transition(is_user_input, hal_cli_socket_, fsm_input,
-                                    additional_info);
-          break;
+    if (!curr_session_) {
+      if (cmd != ConfUiCmd::kStart) {
+        ConfUiLog(VERBOSE) << ToString(cmd) << " to " << session_id
+                           << " is ignored as there is no session to receive";
+        continue;
       }
+      // the session is created as kInit
+      curr_session_ = CreateSession(input.GetSessionId());
     }
-
-    // check the session if it is inactive (e.g. init, suspended)
-    // and if it is done (transitioned to init from any other state)
-    if (curr_session_->IsSuspended()) {
-      session_map_[GetCurrentSessionId()] = std::move(curr_session_);
-      curr_session_ = nullptr;
-      continue;
+    if (cmd == ConfUiCmd::kUserTouchEvent) {
+      ConfUiSecureUserTouchMessage& touch_event =
+          static_cast<ConfUiSecureUserTouchMessage&>(input);
+      auto [x, y] = touch_event.GetLocation();
+      const bool is_confirm = curr_session_->IsConfirm(x, y);
+      const bool is_cancel = curr_session_->IsCancel(x, y);
+      if (!is_confirm && !is_cancel) {
+        // ignore, take the next input
+        continue;
+      }
+      decltype(input_ptr) tmp_input_ptr =
+          std::make_unique<ConfUiUserSelectionMessage>(
+              GetCurrentSessionId(),
+              (is_confirm ? UserResponse::kConfirm : UserResponse::kCancel));
+      input_ptr = WrapWithSecureFlag(*tmp_input_ptr, touch_event.IsSecure());
     }
+    Transition(input_ptr);
 
-    if (curr_session_->GetState() == MainLoopState::kAwaitCleanup) {
+    // finalize
+    if (curr_session_ &&
+        curr_session_->GetState() == MainLoopState::kAwaitCleanup) {
       curr_session_->CleanUp();
       curr_session_ = nullptr;
     }
-    // otherwise, continue with keeping the curr_session_
   }  // end of the infinite while loop
 }
 
+std::shared_ptr<Session> HostServer::CreateSession(const std::string& name) {
+  return std::make_shared<Session>(name, display_num_, host_mode_ctrl_,
+                                   screen_connector_);
+}
+
+static bool IsUserAbort(ConfUiMessage& msg) {
+  if (msg.GetType() != ConfUiCmd::kUserInputEvent) {
+    return false;
+  }
+  ConfUiUserSelectionMessage& selection =
+      static_cast<ConfUiUserSelectionMessage&>(msg);
+  return (selection.GetResponse() == UserResponse::kUserAbort);
+}
+
+void HostServer::Transition(std::unique_ptr<ConfUiMessage>& input_ptr) {
+  auto& input = *input_ptr;
+  const auto session_id = input.GetSessionId();
+  const auto cmd = input.GetType();
+  const std::string cmd_str(ToString(cmd));
+  FsmInput fsm_input = ToFsmInput(input);
+  ConfUiLog(VERBOSE) << "Handling " << ToString(cmd);
+  if (IsUserAbort(input)) {
+    curr_session_->UserAbort(hal_cli_socket_);
+    return;
+  }
+
+  if (cmd == ConfUiCmd::kAbort) {
+    curr_session_->Abort();
+    return;
+  }
+  curr_session_->Transition(hal_cli_socket_, fsm_input, input);
+}
+
 }  // end of namespace confui
 }  // end of namespace cuttlefish
diff --git a/host/libs/confui/host_server.h b/host/libs/confui/host_server.h
index be5caeb..904af33 100644
--- a/host/libs/confui/host_server.h
+++ b/host/libs/confui/host_server.h
@@ -35,7 +35,6 @@
 #include "host/commands/kernel_log_monitor/utils.h"
 #include "host/libs/config/logging.h"
 #include "host/libs/confui/host_mode_ctrl.h"
-#include "host/libs/confui/host_renderer.h"
 #include "host/libs/confui/host_virtual_input.h"
 #include "host/libs/confui/server_common.h"
 #include "host/libs/confui/session.h"
@@ -50,11 +49,11 @@
       cuttlefish::ScreenConnectorFrameRenderer& screen_connector);
 
   void Start();  // start this server itself
-  virtual ~HostServer() = default;
+  virtual ~HostServer() {}
 
-  // implement input interfaces. called by webRTC & vnc
-  void PressConfirmButton(const bool is_down) override;
-  void PressCancelButton(const bool is_down) override;
+  // implement input interfaces. called by webRTC
+  void TouchEvent(const int x, const int y, const bool is_down) override;
+  void UserAbortEvent() override;
   bool IsConfUiActive() override;
 
  private:
@@ -89,7 +88,7 @@
    *  should render the Android guest frames but keep the confirmation
    *  UI session and frame
    *
-   *  The inputs are I = {u, g}. 'u' is the user input from vnc/webRTC
+   *  The inputs are I = {u, g}. 'u' is the user input from webRTC
    *  clients. Note that the host service serialized the concurrent user
    *  inputs from multiple clients. 'g' is the command from the HAL service
    *
@@ -115,21 +114,10 @@
 
   SharedFD EstablishHalConnection();
 
-  // failed to start dialog, etc
-  // basically, will reset the session, so start from the beginning in the same
-  // session
-  void ResetOnCommandFailure();
+  std::shared_ptr<Session> CreateSession(const std::string& session_name);
+  void SendUserSelection(std::unique_ptr<ConfUiMessage>& input);
 
-  // note: the picked session will be removed from session_map_
-  std::unique_ptr<Session> GetSession(const std::string& session_id) {
-    if (session_map_.find(session_id) == session_map_.end()) {
-      return nullptr;
-    }
-    std::unique_ptr<Session> temp = std::move(session_map_[session_id]);
-    session_map_.erase(session_id);
-    return temp;
-  }
-
+  void Transition(std::unique_ptr<ConfUiMessage>& input_ptr);
   std::string GetCurrentSessionId() {
     if (curr_session_) {
       return curr_session_->GetId();
@@ -143,29 +131,23 @@
     }
     return ToString(curr_session_->GetState());
   }
-  std::unique_ptr<Session> ComputeCurrentSession(const std::string& session_id);
-  bool SendUserSelection(UserResponse::type selection);
 
   const std::uint32_t display_num_;
   HostModeCtrl& host_mode_ctrl_;
   ScreenConnectorFrameRenderer& screen_connector_;
 
-  // this member creates a raw frame
-  ConfUiRenderer renderer_;
-
   std::string input_socket_path_;
-  std::string hal_socket_path_;
+  int hal_vsock_port_;
 
-  // session id to Session object map, for those that are suspended
-  std::unordered_map<std::string, std::unique_ptr<Session>> session_map_;
-  // curr_session_ doesn't belong to session_map_
-  std::unique_ptr<Session> curr_session_;
+  std::shared_ptr<Session> curr_session_;
 
   SharedFD guest_hal_socket_;
   // ACCEPTED fd on guest_hal_socket_
   SharedFD hal_cli_socket_;
-  std::mutex input_socket_mtx_;
 
+  using Multiplexer =
+      Multiplexer<std::unique_ptr<ConfUiMessage>,
+                  ThreadSafeQueue<std::unique_ptr<ConfUiMessage>>>;
   /*
    * Multiplexer has N queues. When pop(), it is going to sleep until
    * there's at least one item in at least one queue. The lower the Q
@@ -174,12 +156,16 @@
    * For HostServer, we have a queue for the user input events, and
    * another for hal cmd/msg queues
    */
-  Multiplexer<ConfUiMessage> input_multiplexer_;
+  Multiplexer input_multiplexer_;
   int hal_cmd_q_id_;         // Q id in input_multiplexer_
   int user_input_evt_q_id_;  // Q id in input_multiplexer_
 
   std::thread main_loop_thread_;
   std::thread hal_input_fetcher_thread_;
+
+  std::mutex socket_flag_mtx_;
+  std::condition_variable socket_flag_cv_;
+  bool is_socket_ok_;
 };
 
 }  // end of namespace confui
diff --git a/host/libs/confui/host_virtual_input.h b/host/libs/confui/host_virtual_input.h
index ec800cb..65ec577 100644
--- a/host/libs/confui/host_virtual_input.h
+++ b/host/libs/confui/host_virtual_input.h
@@ -23,13 +23,13 @@
 enum class ConfUiKeys : std::uint32_t { Confirm = 7, Cancel = 8 };
 
 /**
- * vnc, webrtc will deliver the user inputs from their client
+ * webrtc will deliver the user inputs from their client
  * to this class object
  */
 class HostVirtualInput {
  public:
-  virtual void PressConfirmButton(const bool is_down) = 0;
-  virtual void PressCancelButton(const bool is_down) = 0;
+  virtual void TouchEvent(const int x, const int y, const bool is_down) = 0;
+  virtual void UserAbortEvent() = 0;
   virtual ~HostVirtualInput() = default;
   // guarantees that if this returns true, it is confirmation UI mode
   virtual bool IsConfUiActive() = 0;
diff --git a/host/libs/confui/layouts/layout.h b/host/libs/confui/layouts/layout.h
index bb40b67..4f6c4dc 100644
--- a/host/libs/confui/layouts/layout.h
+++ b/host/libs/confui/layouts/layout.h
@@ -29,38 +29,13 @@
 
 DECLARE_PARAMETER(RightEdgeOfScreen);
 DECLARE_PARAMETER(BottomOfScreen);
-DECLARE_PARAMETER(PowerButtonTop);
-DECLARE_PARAMETER(PowerButtonBottom);
-DECLARE_PARAMETER(VolUpButtonTop);
-DECLARE_PARAMETER(VolUpButtonBottom);
 DECLARE_PARAMETER(DefaultFontSize);  // 14_dp regular and 18_dp magnified
 DECLARE_PARAMETER(BodyFontSize);     // 16_dp regular and 20_dp magnified
 DECLARE_TYPED_PARAMETER(ShieldColor, ::teeui::Color);
 DECLARE_TYPED_PARAMETER(ColorText, ::teeui::Color);
 DECLARE_TYPED_PARAMETER(ColorBG, ::teeui::Color);
 
-NEW_PARAMETER_SET(ConUIParameters, RightEdgeOfScreen, BottomOfScreen,
-                  PowerButtonTop, PowerButtonBottom, VolUpButtonTop,
-                  VolUpButtonBottom, DefaultFontSize, BodyFontSize, ShieldColor,
-                  ColorText, ColorBG);
-
 CONSTANT(BorderWidth, 24_dp);
-CONSTANT(PowerButtonCenter, (PowerButtonTop() + PowerButtonBottom()) / 2_px);
-CONSTANT(VolUpButtonCenter, (VolUpButtonTop() + VolUpButtonBottom()) / 2.0_px);
-CONSTANT(GrayZone, 12_dp);
-CONSTANT(RightLabelEdge, RightEdgeOfScreen() - BorderWidth - GrayZone);
-CONSTANT(LabelWidth, RightLabelEdge - BorderWidth);
-
-CONSTANT(SQRT2, 1.4142135623_dp);
-CONSTANT(SQRT8, 2.828427125_dp);
-
-CONSTANT(ARROW_SHAPE,
-         CONVEX_OBJECTS(
-             CONVEX_OBJECT(Vec2d{.0_dp, .0_dp}, Vec2d{6.0_dp, 6.0_dp},
-                           Vec2d{6.0_dp - SQRT8, 6.0_dp}, Vec2d{-SQRT2, SQRT2}),
-             CONVEX_OBJECT(Vec2d{6.0_dp - SQRT8, 6.0_dp}, Vec2d{6.0_dp, 6.0_dp},
-                           Vec2d{0.0_dp, 12.0_dp},
-                           Vec2d{-SQRT2, 12.0_dp - SQRT2})));
 
 DECLARE_FONT_BUFFER(RobotoMedium, RobotoMedium, RobotoMedium_length);
 DECLARE_FONT_BUFFER(RobotoRegular, RobotoRegular, RobotoRegular_length);
@@ -68,63 +43,32 @@
 
 CONSTANT(DefaultFont, FONT(RobotoRegular));
 
-BEGIN_ELEMENT(LabelOK, teeui::Label)
-FontSize(DefaultFontSize());
-LineHeight(20_dp);
-NumberOfLines(2);
-Dimension(LabelWidth, HeightFromLines);
-Position(BorderWidth, PowerButtonCenter - dim_h / 2.0_px);
-DefaultText("Press Power Button Confirm");
-RightJustified;
-VerticallyCentered;
-TextColor(ColorText());
-Font(FONT(RobotoMedium));
-TextID(TEXT_ID(TranslationId::CONFIRM_PWR_BUTTON_DOUBLE_PRESS));
-END_ELEMENT();
+DECLARE_TYPED_PARAMETER(ColorButton, ::teeui::Color);
 
-BEGIN_ELEMENT(IconPower, teeui::Button, ConvexObjectCount(2))
-Dimension(BorderWidth, PowerButtonBottom() - PowerButtonTop());
-Position(RightEdgeOfScreen() - BorderWidth, PowerButtonTop());
-CornerRadius(3_dp);
-ButtonColor(ColorText());
-RoundTopLeft;
-RoundBottomLeft;
-ConvexObjectColor(ColorBG());
-ConvexObjects(ARROW_SHAPE);
-END_ELEMENT();
+NEW_PARAMETER_SET(ConfUIParameters, RightEdgeOfScreen, BottomOfScreen,
+                  DefaultFontSize, BodyFontSize, ShieldColor, ColorText,
+                  ColorBG, ColorButton);
 
-BEGIN_ELEMENT(LabelCancel, teeui::Label)
-FontSize(DefaultFontSize());
-LineHeight(20_dp);
-NumberOfLines(2);
-Dimension(LabelWidth, HeightFromLines);
-Position(BorderWidth, VolUpButtonCenter - dim_h / 2.0_px);
-DefaultText("Press Menu Button to Cancel");
-RightJustified;
-VerticallyCentered;
-TextColor(ColorText());
-Font(FONT(RobotoMedium));
-TextID(TEXT_ID(TranslationId::CANCEL));
-END_ELEMENT();
-
-BEGIN_ELEMENT(IconVolUp, teeui::Button, ConvexObjectCount(2))
-Dimension(BorderWidth, VolUpButtonBottom() - VolUpButtonTop());
-Position(RightEdgeOfScreen() - BorderWidth, VolUpButtonTop());
-CornerRadius(5_dp);
-ButtonColor(ColorBG());
-ConvexObjectColor(ColorText());
-ConvexObjects(ARROW_SHAPE);
-END_ELEMENT();
+CONSTANT(IconShieldDistanceFromTop, 100_dp);
+CONSTANT(LabelBorderZone, 4_dp);
+CONSTANT(RightLabelEdge, RightEdgeOfScreen() - BorderWidth);
+CONSTANT(LabelWidth, RightLabelEdge - BorderWidth);
+CONSTANT(ButtonHeight, 72_dp);
+CONSTANT(ButtonPositionX, 0);
+CONSTANT(ButtonPositionY, BottomOfScreen() - ButtonHeight);
+CONSTANT(ButtonWidth, 130_dp);
+CONSTANT(ButtonLabelDistance, 12_dp);
 
 BEGIN_ELEMENT(IconShield, teeui::Label)
 FontSize(24_dp);
 LineHeight(24_dp);
 NumberOfLines(1);
 Dimension(LabelWidth, HeightFromLines);
-Position(BorderWidth, BOTTOM_EDGE_OF(LabelCancel) + 40_dp);
+Position(BorderWidth, IconShieldDistanceFromTop);
 DefaultText(
     "A");  // ShieldTTF has just one glyph at the code point for capital A
 TextColor(ShieldColor());
+HorizontalTextAlignment(Alignment::CENTER);
 Font(FONT(Shield));
 END_ELEMENT();
 
@@ -132,8 +76,8 @@
 FontSize(20_dp);
 LineHeight(20_dp);
 NumberOfLines(1);
-Dimension(RightEdgeOfScreen() - BorderWidth, HeightFromLines);
-Position(BorderWidth, BOTTOM_EDGE_OF(IconShield) + 12_dp);
+Dimension(LabelWidth, HeightFromLines);
+Position(BorderWidth, BOTTOM_EDGE_OF(IconShield) + 16_dp);
 DefaultText("Android Protected Confirmation");
 Font(FONT(RobotoMedium));
 VerticallyCentered;
@@ -141,12 +85,56 @@
 TextID(TEXT_ID(TranslationId::TITLE));
 END_ELEMENT();
 
+BEGIN_ELEMENT(IconOk, teeui::Button, ConvexObjectCount(1))
+Dimension(ButtonWidth, ButtonHeight - BorderWidth);
+Position(RightEdgeOfScreen() - ButtonWidth - BorderWidth,
+         ButtonPositionY + ButtonLabelDistance);
+CornerRadius(4_dp);
+ButtonColor(ColorButton());
+RoundTopLeft;
+RoundBottomLeft;
+RoundTopRight;
+RoundBottomRight;
+END_ELEMENT();
+
+BEGIN_ELEMENT(LabelOK, teeui::Label)
+FontSize(BodyFontSize());
+LineHeight(BodyFontSize() * 1.4_px);
+NumberOfLines(1);
+Dimension(ButtonWidth - (LabelBorderZone * 2_dp),
+          ButtonHeight - BorderWidth - (LabelBorderZone * 2_dp));
+Position(RightEdgeOfScreen() - ButtonWidth - BorderWidth + LabelBorderZone,
+         ButtonPositionY + ButtonLabelDistance + LabelBorderZone);
+DefaultText("Confirm");
+Font(FONT(RobotoMedium));
+HorizontalTextAlignment(Alignment::CENTER);
+VerticalTextAlignment(Alignment::CENTER);
+TextColor(ColorBG());
+TextID(TEXT_ID(TranslationId::CONFIRM));
+END_ELEMENT();
+
+BEGIN_ELEMENT(LabelCancel, teeui::Label)
+FontSize(BodyFontSize());
+LineHeight(BodyFontSize() * 1.4_px);
+NumberOfLines(1);
+Dimension(ButtonWidth - (LabelBorderZone * 2_dp),
+          ButtonHeight - BorderWidth - (LabelBorderZone * 2_dp));
+Position(BorderWidth + LabelBorderZone,
+         ButtonPositionY + ButtonLabelDistance + LabelBorderZone);
+DefaultText("Cancel");
+HorizontalTextAlignment(Alignment::LEFT);
+Font(FONT(RobotoMedium));
+VerticallyCentered;
+TextColor(ColorButton());
+TextID(TEXT_ID(TranslationId::CANCEL));
+END_ELEMENT();
+
 BEGIN_ELEMENT(LabelHint, teeui::Label)
 FontSize(DefaultFontSize());
 LineHeight(DefaultFontSize() * 1.5_px);
 NumberOfLines(4);
 Dimension(LabelWidth, HeightFromLines);
-Position(BorderWidth, BottomOfScreen() - BorderWidth - dim_h);
+Position(BorderWidth, ButtonPositionY - dim_h - 48_dp);
 DefaultText(
     "This confirmation provides an extra layer of security for the action "
     "you're "
@@ -161,7 +149,7 @@
 FontSize(BodyFontSize());
 LineHeight(BodyFontSize() * 1.4_px);
 NumberOfLines(20);
-Position(BorderWidth, BOTTOM_EDGE_OF(LabelTitle) + 18_dp);
+Position(BorderWidth, BOTTOM_EDGE_OF(LabelTitle) + 16_dp);
 Dimension(LabelWidth, LabelHint::pos_y - pos_y - 24_dp);
 DefaultText(
     "12345678901234567890123456789012345678901234567890123456789012345678901234"
@@ -171,7 +159,7 @@
 Font(FONT(RobotoRegular));
 END_ELEMENT();
 
-NEW_LAYOUT(ConfUILayout, LabelOK, IconPower, LabelCancel, IconVolUp, IconShield,
-           LabelTitle, LabelHint, LabelBody);
+NEW_LAYOUT(ConfUILayout, IconShield, LabelTitle, LabelHint, LabelBody, IconOk,
+           LabelOK, LabelCancel);
 
 }  // namespace teeui
diff --git a/host/libs/confui/secure_input.cc b/host/libs/confui/secure_input.cc
new file mode 100644
index 0000000..672ed3d
--- /dev/null
+++ b/host/libs/confui/secure_input.cc
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/confui/secure_input.h"
+
+namespace cuttlefish {
+namespace confui {
+
+std::unique_ptr<ConfUiSecureUserSelectionMessage> ToSecureSelectionMessage(
+    const ConfUiUserSelectionMessage& msg, const bool secure) {
+  return std::make_unique<ConfUiSecureUserSelectionMessage>(msg, secure);
+}
+
+std::unique_ptr<ConfUiSecureUserTouchMessage> ToSecureTouchMessage(
+    const ConfUiUserTouchMessage& msg, const bool secure) {
+  return std::make_unique<ConfUiSecureUserTouchMessage>(msg, secure);
+}
+
+}  // end of namespace confui
+}  // end of namespace cuttlefish
diff --git a/host/libs/confui/secure_input.h b/host/libs/confui/secure_input.h
new file mode 100644
index 0000000..527fcd7
--- /dev/null
+++ b/host/libs/confui/secure_input.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include "common/libs/confui/confui.h"
+
+/** ConfUiUserSelectionMessage with a security flag
+ *
+ * Inputs generated by something that belong to (virtualized) TEE is regarded
+ * as secure. Otherwise (e.g. inputs generated by the guest calling
+ * deliverSecureInputEvent), it is regarded as insecure.
+ *
+ * The host marks the security field, and use it internally and exclusively.
+ *
+ */
+namespace cuttlefish {
+namespace confui {
+class ConfUiSecureUserSelectionMessage : public ConfUiMessage {
+ public:
+  ConfUiSecureUserSelectionMessage(const ConfUiUserSelectionMessage& msg,
+                                   const bool secure)
+      : ConfUiMessage(msg.GetSessionId()), msg_(msg), is_secure_(secure) {}
+  ConfUiSecureUserSelectionMessage() = delete;
+  virtual ~ConfUiSecureUserSelectionMessage() = default;
+  std::string ToString() const override { return msg_.ToString(); }
+  ConfUiCmd GetType() const override { return msg_.GetType(); }
+  auto GetResponse() const { return msg_.GetResponse(); }
+  // SendOver is between guest and host, so it doesn't send the is_secure_
+  bool SendOver(SharedFD fd) override { return msg_.SendOver(fd); }
+  bool IsSecure() const { return is_secure_; }
+  // SetSecure() might be needed later on but not now.
+
+ private:
+  ConfUiUserSelectionMessage msg_;
+  bool is_secure_;
+};
+
+class ConfUiSecureUserTouchMessage : public ConfUiMessage {
+ public:
+  ConfUiSecureUserTouchMessage(const ConfUiUserTouchMessage& msg,
+                               const bool secure)
+      : ConfUiMessage(msg.GetSessionId()), msg_(msg), is_secure_(secure) {}
+  virtual ~ConfUiSecureUserTouchMessage() = default;
+  std::string ToString() const override { return msg_.ToString(); }
+  ConfUiCmd GetType() const override { return msg_.GetType(); }
+  auto GetResponse() const { return msg_.GetResponse(); }
+  bool SendOver(SharedFD fd) override { return msg_.SendOver(fd); }
+  std::pair<int, int> GetLocation() { return msg_.GetLocation(); }
+  bool IsSecure() const { return is_secure_; }
+
+ private:
+  ConfUiUserTouchMessage msg_;
+  bool is_secure_;
+};
+
+std::unique_ptr<ConfUiSecureUserSelectionMessage> ToSecureSelectionMessage(
+    const ConfUiUserSelectionMessage& msg, const bool secure);
+std::unique_ptr<ConfUiSecureUserTouchMessage> ToSecureTouchMessage(
+    const ConfUiUserTouchMessage& msg, const bool secure);
+}  // end of namespace confui
+}  // end of namespace cuttlefish
diff --git a/host/libs/confui/server_common.cc b/host/libs/confui/server_common.cc
index cf46fe3..66adb55 100644
--- a/host/libs/confui/server_common.cc
+++ b/host/libs/confui/server_common.cc
@@ -17,60 +17,37 @@
 #include "host/libs/confui/server_common.h"
 namespace cuttlefish {
 namespace confui {
-static FsmInput UserEvtToFsmInput(UserResponse::type user_response) {
-  if (user_response == UserResponse::kConfirm) {
-    return FsmInput::kUserUnknown;
-  }
-  if (user_response == UserResponse::kCancel) {
-    return FsmInput::kUserCancel;
-  }
-  return FsmInput::kUserUnknown;
-}
-
-FsmInput ToFsmInput(const ConfUiMessage& confui_msg) {
-  ConfUiCmd cmd = ToCmd(confui_msg.type_);
-  if (cmd == ConfUiCmd::kUserInputEvent) {
-    return UserEvtToFsmInput(confui_msg.msg_);
-  }
-  const auto hal_cmd = cmd;
-  switch (hal_cmd) {
+FsmInput ToFsmInput(const ConfUiMessage& msg) {
+  const auto cmd = msg.GetType();
+  switch (cmd) {
+    case ConfUiCmd::kUserInputEvent:
+      return FsmInput::kUserEvent;
     case ConfUiCmd::kUnknown:
       return FsmInput::kHalUnknown;
     case ConfUiCmd::kStart:
       return FsmInput::kHalStart;
     case ConfUiCmd::kStop:
       return FsmInput::kHalStop;
-    case ConfUiCmd::kSuspend:
-      return FsmInput::kHalSuspend;
-    case ConfUiCmd::kRestore:
-      return FsmInput::kHalRestore;
     case ConfUiCmd::kAbort:
       return FsmInput::kHalAbort;
     case ConfUiCmd::kCliAck:
     case ConfUiCmd::kCliRespond:
     default:
-      ConfUiLog(FATAL) << "The" << ToString(hal_cmd)
-                       << "is not handled by Session";
+      ConfUiLog(FATAL) << "The" << ToString(cmd)
+                       << "is not handled by the Session FSM but "
+                       << "directly calls Abort()";
   }
   return FsmInput::kHalUnknown;
 }
 
 std::string ToString(FsmInput input) {
   switch (input) {
-    case FsmInput::kUserConfirm:
-      return {"kUserConfirm"};
-    case FsmInput::kUserCancel:
-      return {"kUserCancel"};
-    case FsmInput::kUserUnknown:
-      return {"kUserUnknown"};
+    case FsmInput::kUserEvent:
+      return {"kUserEvent"};
     case FsmInput::kHalStart:
       return {"kHalStart"};
     case FsmInput::kHalStop:
       return {"kHalStop"};
-    case FsmInput::kHalSuspend:
-      return {"kHalSuspend"};
-    case FsmInput::kHalRestore:
-      return {"kHalRestore"};
     case FsmInput::kHalAbort:
       return {"kHalAbort"};
     case FsmInput::kHalUnknown:
@@ -88,10 +65,10 @@
       return "kInSession";
     case MainLoopState::kWaitStop:
       return "kWaitStop";
-    case MainLoopState::kSuspended:
-      return "kSuspended";
     case MainLoopState::kAwaitCleanup:
       return "kAwaitCleanup";
+    case MainLoopState::kTerminated:
+      return "kTerminated";
     default:
       return "kInvalid";
   }
diff --git a/host/libs/confui/server_common.h b/host/libs/confui/server_common.h
index 6175d16..8ffd578 100644
--- a/host/libs/confui/server_common.h
+++ b/host/libs/confui/server_common.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <cstdint>
+#include <memory>
 #include <vector>
 
 #include "common/libs/confui/confui.h"
@@ -27,8 +28,8 @@
   kInit = 1,
   kInSession = 2,
   kWaitStop = 3,  // wait ack after sending confirm/cancel
-  kSuspended = 4,
   kAwaitCleanup = 5,
+  kTerminated = 8,
   kInvalid = 9
 };
 
@@ -36,13 +37,9 @@
 
 // FSM input to Session FSM
 enum class FsmInput : std::uint32_t {
-  kUserConfirm,
-  kUserCancel,
-  kUserUnknown,
+  kUserEvent = 1,
   kHalStart,
   kHalStop,
-  kHalSuspend,
-  kHalRestore,
   kHalAbort,
   kHalUnknown
 };
@@ -50,7 +47,9 @@
 std::string ToString(FsmInput input);
 std::string ToString(const MainLoopState& state);
 
-FsmInput ToFsmInput(const ConfUiMessage& confui_msg);
+FsmInput ToFsmInput(const ConfUiMessage& msg);
 
+std::unique_ptr<ConfUiMessage> CreateFromUserSelection(
+    const std::string& session_id, const UserResponse::type user_selection);
 }  // end of namespace confui
 }  // end of namespace cuttlefish
diff --git a/host/libs/confui/session.cc b/host/libs/confui/session.cc
index 891250d..2b7069b 100644
--- a/host/libs/confui/session.cc
+++ b/host/libs/confui/session.cc
@@ -16,22 +16,47 @@
 
 #include "host/libs/confui/session.h"
 
+#include <algorithm>
+
+#include "host/libs/confui/secure_input.h"
+
 namespace cuttlefish {
 namespace confui {
 
-Session::Session(const std::string& session_id, const std::uint32_t display_num,
-                 ConfUiRenderer& host_renderer, HostModeCtrl& host_mode_ctrl,
+Session::Session(const std::string& session_name,
+                 const std::uint32_t display_num, HostModeCtrl& host_mode_ctrl,
                  ScreenConnectorFrameRenderer& screen_connector,
                  const std::string& locale)
-    : session_id_{session_id},
+    : session_id_{session_name},
       display_num_{display_num},
-      renderer_{host_renderer},
       host_mode_ctrl_{host_mode_ctrl},
       screen_connector_{screen_connector},
       locale_{locale},
       state_{MainLoopState::kInit},
       saved_state_{MainLoopState::kInit} {}
 
+/** return grace period + alpha
+ *
+ * grace period is the gap between user seeing the dialog
+ * and the UI starts to take the user inputs
+ * Grace period should be at least 1s.
+ * Session requests the Renderer to render the dialog,
+ * but it might not be immediate. So, add alpha to 1s
+ */
+static const std::chrono::milliseconds GetGracePeriod() {
+  using std::literals::chrono_literals::operator""ms;
+  return 1000ms + 100ms;
+}
+
+bool Session::IsReadyForUserInput() const {
+  using std::literals::chrono_literals::operator""ms;
+  if (!start_time_) {
+    return false;
+  }
+  const auto right_now = Clock::now();
+  return (right_now - *start_time_) >= GetGracePeriod();
+}
+
 bool Session::IsConfUiActive() const {
   if (state_ == MainLoopState::kInSession ||
       state_ == MainLoopState::kWaitStop) {
@@ -40,234 +65,241 @@
   return false;
 }
 
-bool Session::RenderDialog(const std::string& msg, const std::string& locale) {
-  auto [teeui_frame, is_success] = renderer_.RenderRawFrame(msg, locale);
-  if (!is_success) {
+template <typename C, typename T>
+static bool Contains(const C& c, T&& item) {
+  auto itr = std::find(c.begin(), c.end(), std::forward<T>(item));
+  return itr != c.end();
+}
+
+bool Session::IsInverted() const {
+  return Contains(ui_options_, teeui::UIOption::AccessibilityInverted);
+}
+
+bool Session::IsMagnified() const {
+  return Contains(ui_options_, teeui::UIOption::AccessibilityMagnified);
+}
+
+bool Session::RenderDialog() {
+  renderer_ = ConfUiRenderer::GenerateRenderer(
+      display_num_, prompt_text_, locale_, IsInverted(), IsMagnified());
+  if (!renderer_) {
     return false;
   }
-  prompt_ = msg;
-  locale_ = locale;
-
-  ConfUiLog(DEBUG) << "actually trying to render the frame"
-                   << thread::GetName();
-  auto frame_width = ScreenConnectorInfo::ScreenWidth(display_num_);
-  auto frame_height = ScreenConnectorInfo::ScreenHeight(display_num_);
-  auto frame_stride_bytes =
-      ScreenConnectorInfo::ScreenStrideBytes(display_num_);
-  auto frame_bytes = reinterpret_cast<std::uint8_t*>(teeui_frame.data());
+  auto teeui_frame = renderer_->RenderRawFrame();
+  if (!teeui_frame) {
+    return false;
+  }
+  ConfUiLog(VERBOSE) << "actually trying to render the frame"
+                     << thread::GetName();
+  auto frame_width = teeui_frame->Width();
+  auto frame_height = teeui_frame->Height();
+  auto frame_stride_bytes = teeui_frame->ScreenStrideBytes();
+  auto frame_bytes = reinterpret_cast<std::uint8_t*>(teeui_frame->data());
   return screen_connector_.RenderConfirmationUi(
       display_num_, frame_width, frame_height, frame_stride_bytes, frame_bytes);
 }
 
-bool Session::IsSuspended() const {
-  return (state_ == MainLoopState::kSuspended);
-}
-
-MainLoopState Session::Transition(const bool is_user_input, SharedFD& hal_cli,
-                                  const FsmInput fsm_input,
-                                  const std::string& additional_info) {
+MainLoopState Session::Transition(SharedFD& hal_cli, const FsmInput fsm_input,
+                                  const ConfUiMessage& conf_ui_message) {
+  bool should_keep_running = false;
+  bool already_terminated = false;
   switch (state_) {
     case MainLoopState::kInit: {
-      HandleInit(is_user_input, hal_cli, fsm_input, additional_info);
+      should_keep_running = HandleInit(hal_cli, fsm_input, conf_ui_message);
     } break;
     case MainLoopState::kInSession: {
-      HandleInSession(is_user_input, hal_cli, fsm_input);
+      should_keep_running =
+          HandleInSession(hal_cli, fsm_input, conf_ui_message);
     } break;
     case MainLoopState::kWaitStop: {
-      if (is_user_input) {
-        ConfUiLog(DEBUG) << "User input ignored" << ToString(fsm_input) << " : "
-                         << additional_info << "at state" << ToString(state_);
+      if (IsUserInput(fsm_input)) {
+        ConfUiLog(VERBOSE) << "User input ignored " << ToString(fsm_input)
+                           << " : " << ToString(conf_ui_message)
+                           << " at the state " << ToString(state_);
       }
-      HandleWaitStop(is_user_input, hal_cli, fsm_input);
+      should_keep_running = HandleWaitStop(hal_cli, fsm_input);
+    } break;
+    case MainLoopState::kTerminated: {
+      already_terminated = true;
     } break;
     default:
-      // host service explicitly calls restore and suspend
-      ConfUiLog(FATAL) << "Must not be in the state of" << ToString(state_);
+      ConfUiLog(FATAL) << "Must not be in the state of " << ToString(state_);
       break;
   }
+  if (!should_keep_running && !already_terminated) {
+    ScheduleToTerminate();
+  }
   return state_;
 };
 
-bool Session::Suspend(SharedFD hal_cli) {
-  if (state_ == MainLoopState::kInit) {
-    // HAL sent wrong command
-    ConfUiLog(FATAL)
-        << "HAL sent wrong command, suspend, when the session is in kIinit";
-    return false;
-  }
-  if (state_ == MainLoopState::kSuspended) {
-    ConfUiLog(DEBUG) << "Already kSuspended state";
-    return false;
-  }
-  saved_state_ = state_;
-  state_ = MainLoopState::kSuspended;
-  host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kAndroidMode);
-  if (!packet::SendAck(hal_cli, session_id_, /*is success*/ true,
-                       "suspended")) {
-    ConfUiLog(FATAL) << "I/O error";
-    return false;
-  }
-  return true;
-}
-
-bool Session::Restore(SharedFD hal_cli) {
-  if (state_ == MainLoopState::kInit) {
-    // HAL sent wrong command
-    ConfUiLog(FATAL)
-        << "HAL sent wrong command, restore, when the session is in kIinit";
-    return false;
-  }
-
-  if (state_ != MainLoopState::kSuspended) {
-    ConfUiLog(DEBUG) << "Already Restored to state " + ToString(state_);
-    return false;
-  }
-  host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kConfUI_Mode);
-  if (!RenderDialog(prompt_, locale_)) {
-    // the confirmation UI is driven by a user app, not running from the start
-    // automatically so that means webRTC/vnc should have been set up
-    ConfUiLog(ERROR) << "Dialog is not rendered. However, it should."
-                     << "No webRTC can't initiate any confirmation UI.";
-    if (!packet::SendAck(hal_cli, session_id_, false,
-                         "render failed in restore")) {
-      ConfUiLog(FATAL) << "Rendering failed in restore, and ack failed in I/O";
-    }
-    state_ = MainLoopState::kInit;
-    return false;
-  }
-  if (!packet::SendAck(hal_cli, session_id_, true, "restored")) {
-    ConfUiLog(FATAL) << "Ack to restore failed in I/O";
-  }
-  state_ = saved_state_;
-  saved_state_ = MainLoopState::kInit;
-  return true;
-}
-
-bool Session::Kill(SharedFD hal_cli, const std::string& response_msg) {
-  state_ = MainLoopState::kAwaitCleanup;
-  saved_state_ = MainLoopState::kInvalid;
-  if (!packet::SendAck(hal_cli, session_id_, true, response_msg)) {
-    ConfUiLog(FATAL) << "I/O error in ack to Abort";
-    return false;
-  }
-  return true;
-}
-
 void Session::CleanUp() {
   if (state_ != MainLoopState::kAwaitCleanup) {
     ConfUiLog(FATAL) << "Clean up a session only when in kAwaitCleanup";
   }
+  state_ = MainLoopState::kTerminated;
   // common action done when the state is back to init state
   host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kAndroidMode);
 }
 
-void Session::ReportErrorToHal(SharedFD hal_cli, const std::string& msg) {
-  // reset the session -- destroy it & recreate it with the same
-  // session id
+void Session::ScheduleToTerminate() {
   state_ = MainLoopState::kAwaitCleanup;
-  if (!packet::SendAck(hal_cli, session_id_, false, msg)) {
-    ConfUiLog(FATAL) << "I/O error in sending ack to report rendering failure";
+  saved_state_ = MainLoopState::kInvalid;
+}
+
+bool Session::ReportErrorToHal(SharedFD hal_cli, const std::string& msg) {
+  ScheduleToTerminate();
+  if (!SendAck(hal_cli, session_id_, false, msg)) {
+    ConfUiLog(ERROR) << "I/O error in sending ack to report rendering failure";
+    return false;
   }
+  return true;
+}
+
+void Session::Abort() {
+  ConfUiLog(VERBOSE) << "Abort is called";
+  ScheduleToTerminate();
   return;
 }
 
-bool Session::Abort(SharedFD hal_cli) { return Kill(hal_cli, "aborted"); }
+void Session::UserAbort(SharedFD hal_cli) {
+  ConfUiLog(VERBOSE) << "it is a user abort input.";
+  SendAbortCmd(hal_cli, GetId());
+  Abort();
+  ScheduleToTerminate();
+}
 
-void Session::HandleInit(const bool is_user_input, SharedFD hal_cli,
-                         const FsmInput fsm_input,
-                         const std::string& additional_info) {
-  using namespace cuttlefish::confui::packet;
-  if (is_user_input) {
+bool Session::HandleInit(SharedFD hal_cli, const FsmInput fsm_input,
+                         const ConfUiMessage& conf_ui_message) {
+  if (IsUserInput(fsm_input)) {
     // ignore user input
     state_ = MainLoopState::kInit;
-    return;
+    return true;
   }
 
-  ConfUiLog(DEBUG) << ToString(fsm_input) << "is handled in HandleInit";
+  ConfUiLog(VERBOSE) << ToString(fsm_input) << "is handled in HandleInit";
   if (fsm_input != FsmInput::kHalStart) {
     ConfUiLog(ERROR) << "invalid cmd for Init State:" << ToString(fsm_input);
-    // reset the session -- destroy it & recreate it with the same
-    // session id
-    ReportErrorToHal(hal_cli, "wrong hal command");
-    return;
+    // ReportErrorToHal returns true if error report was successful
+    // However, anyway we abort this session on the host
+    ReportErrorToHal(hal_cli, HostError::kSystemError);
+    return false;
   }
 
   // Start Session
-  ConfUiLog(DEBUG) << "Sending ack to hal_cli: "
-                   << Enum2Base(ConfUiCmd::kCliAck);
+  ConfUiLog(VERBOSE) << "Sending ack to hal_cli: "
+                     << Enum2Base(ConfUiCmd::kCliAck);
   host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kConfUI_Mode);
-  auto confirmation_msg = additional_info;
-  if (!RenderDialog(confirmation_msg, locale_)) {
+
+  auto start_cmd_msg = static_cast<const ConfUiStartMessage&>(conf_ui_message);
+  prompt_text_ = start_cmd_msg.GetPromptText();
+  locale_ = start_cmd_msg.GetLocale();
+  extra_data_ = start_cmd_msg.GetExtraData();
+  ui_options_ = start_cmd_msg.GetUiOpts();
+
+  // cbor_ can be correctly created after the session received kStart cmd
+  // at runtime
+  cbor_ = std::make_unique<Cbor>(prompt_text_, extra_data_);
+  if (cbor_->IsMessageTooLong()) {
+    ConfUiLog(ERROR) << "The prompt text and extra_data are too long to be "
+                     << "properly encoded.";
+    ReportErrorToHal(hal_cli, HostError::kMessageTooLongError);
+    return false;
+  }
+  if (cbor_->IsMalformedUtf8()) {
+    ConfUiLog(ERROR) << "The prompt text appears to have incorrect UTF8 format";
+    ReportErrorToHal(hal_cli, HostError::kIncorrectUTF8);
+    return false;
+  }
+  if (!cbor_->IsOk()) {
+    ConfUiLog(ERROR) << "Unknown Error in cbor implementation";
+    ReportErrorToHal(hal_cli, HostError::kSystemError);
+    return false;
+  }
+
+  if (!RenderDialog()) {
     // the confirmation UI is driven by a user app, not running from the start
-    // automatically so that means webRTC/vnc should have been set up
+    // automatically so that means webRTC should have been set up
     ConfUiLog(ERROR) << "Dialog is not rendered. However, it should."
                      << "No webRTC can't initiate any confirmation UI.";
-    ReportErrorToHal(hal_cli, "rendering failed");
-    return;
+    ReportErrorToHal(hal_cli, HostError::kUIError);
+    return false;
   }
-  if (!packet::SendAck(hal_cli, session_id_, true, "started")) {
-    ConfUiLog(FATAL) << "Ack to kStart failed in I/O";
+  start_time_ = std::make_unique<TimePoint>(std::move(Clock::now()));
+  if (!SendAck(hal_cli, session_id_, true, "started")) {
+    ConfUiLog(ERROR) << "Ack to kStart failed in I/O";
+    return false;
   }
   state_ = MainLoopState::kInSession;
-  return;
+  return true;
 }
 
-void Session::HandleInSession(const bool is_user_input, SharedFD hal_cli,
-                              const FsmInput fsm_input) {
-  if (!is_user_input) {
-    ConfUiLog(FATAL) << "cmd" << ToString(fsm_input)
-                     << "should not be handled in HandleInSession";
-    ReportErrorToHal(hal_cli, "wrong hal command");
-    return;
+bool Session::HandleInSession(SharedFD hal_cli, const FsmInput fsm_input,
+                              const ConfUiMessage& conf_ui_msg) {
+  auto invalid_input_handler = [&, this]() {
+    ReportErrorToHal(hal_cli, HostError::kSystemError);
+    ConfUiLog(ERROR) << "cmd " << ToString(fsm_input)
+                     << " should not be handled in HandleInSession";
+  };
+
+  if (!IsUserInput(fsm_input)) {
+    invalid_input_handler();
+    return false;
   }
 
-  // send to hal_cli either confirm or cancel
-  if (fsm_input != FsmInput::kUserConfirm &&
-      fsm_input != FsmInput::kUserCancel) {
-    /*
-     * TODO(kwstephenkim@google.com): change here when other user inputs must
-     * be handled
-     *
-     */
-    if (!packet::SendAck(hal_cli, session_id_, true,
-                         "invalid user input error")) {
-      // note that input is what we control in memory
-      ConfUiCheck(false) << "Input must be either confirm or cancel for now.";
+  const auto& user_input_msg =
+      static_cast<const ConfUiSecureUserSelectionMessage&>(conf_ui_msg);
+  const auto response = user_input_msg.GetResponse();
+  if (response == UserResponse::kUnknown ||
+      response == UserResponse::kUserAbort) {
+    invalid_input_handler();
+    return false;
+  }
+  const bool is_secure_input = user_input_msg.IsSecure();
+
+  ConfUiLog(VERBOSE) << "In HandleInSession, session " << session_id_
+                     << " is sending the user input " << ToString(fsm_input);
+
+  bool is_success = false;
+  if (response == UserResponse::kCancel) {
+    // no need to sign
+    is_success =
+        SendResponse(hal_cli, session_id_, UserResponse::kCancel,
+                     std::vector<std::uint8_t>{}, std::vector<std::uint8_t>{});
+  } else {
+    message_ = std::move(cbor_->GetMessage());
+    auto message_opt = (is_secure_input ? Sign(message_) : TestSign(message_));
+    if (!message_opt) {
+      ReportErrorToHal(hal_cli, HostError::kSystemError);
+      return false;
     }
-    return;
+    signed_confirmation_ = message_opt.value();
+    is_success = SendResponse(hal_cli, session_id_, UserResponse::kConfirm,
+                              signed_confirmation_, message_);
   }
 
-  ConfUiLog(DEBUG) << "In HandlieInSession, session" << session_id_
-                   << "is sending the user input" << ToString(fsm_input);
-  auto selection = UserResponse::kConfirm;
-  if (fsm_input == FsmInput::kUserCancel) {
-    selection = UserResponse::kCancel;
-  }
-  if (!packet::SendResponse(hal_cli, session_id_, selection)) {
-    ConfUiLog(FATAL) << "I/O error in sending user response to HAL";
+  if (!is_success) {
+    ConfUiLog(ERROR) << "I/O error in sending user response to HAL";
+    return false;
   }
   state_ = MainLoopState::kWaitStop;
-  return;
+  return true;
 }
 
-void Session::HandleWaitStop(const bool is_user_input, SharedFD hal_cli,
-                             const FsmInput fsm_input) {
-  using namespace cuttlefish::confui::packet;
-
-  if (is_user_input) {
+bool Session::HandleWaitStop(SharedFD hal_cli, const FsmInput fsm_input) {
+  if (IsUserInput(fsm_input)) {
     // ignore user input
     state_ = MainLoopState::kWaitStop;
-    return;
+    return true;
   }
   if (fsm_input == FsmInput::kHalStop) {
-    ConfUiLog(DEBUG) << "Handling Abort in kWaitStop.";
-    Kill(hal_cli, "stopped");
-    return;
+    ConfUiLog(VERBOSE) << "Handling Abort in kWaitStop.";
+    ScheduleToTerminate();
+    return true;
   }
+  ReportErrorToHal(hal_cli, HostError::kSystemError);
   ConfUiLog(FATAL) << "In WaitStop, received wrong HAL command "
                    << ToString(fsm_input);
-  state_ = MainLoopState::kAwaitCleanup;
-  return;
+  return false;
 }
 
 }  // end of namespace confui
diff --git a/host/libs/confui/session.h b/host/libs/confui/session.h
index 87e98c9..1afccac 100644
--- a/host/libs/confui/session.h
+++ b/host/libs/confui/session.h
@@ -16,13 +16,19 @@
 
 #pragma once
 
+#include <atomic>
+#include <chrono>
 #include <memory>
+#include <string>
+
+#include <teeui/msg_formatting.h>
 
 #include "common/libs/confui/confui.h"
+#include "host/libs/confui/cbor.h"
 #include "host/libs/confui/host_mode_ctrl.h"
 #include "host/libs/confui/host_renderer.h"
 #include "host/libs/confui/server_common.h"
-#include "host/libs/confui/session.h"
+#include "host/libs/confui/sign.h"
 #include "host/libs/screen_connector/screen_connector.h"
 
 namespace cuttlefish {
@@ -37,8 +43,8 @@
  */
 class Session {
  public:
-  Session(const std::string& session_id, const std::uint32_t display_num,
-          ConfUiRenderer& host_renderer, HostModeCtrl& host_mode_ctrl,
+  Session(const std::string& session_name, const std::uint32_t display_num,
+          HostModeCtrl& host_mode_ctrl,
           ScreenConnectorFrameRenderer& screen_connector,
           const std::string& locale = "en");
 
@@ -48,9 +54,8 @@
 
   MainLoopState GetState() { return state_; }
 
-  MainLoopState Transition(const bool is_user_input, SharedFD& hal_cli,
-                           const FsmInput fsm_input,
-                           const std::string& additional_info);
+  MainLoopState Transition(SharedFD& hal_cli, const FsmInput fsm_input,
+                           const ConfUiMessage& conf_ui_message);
 
   /**
    * this make a transition from kWaitStop or kInSession to kSuspend
@@ -63,49 +68,82 @@
   bool Restore(SharedFD hal_cli);
 
   // abort session
-  bool Abort(SharedFD hal_cli);
+  void Abort();
+
+  // client on the host wants to abort
+  // should let the guest know it
+  void UserAbort(SharedFD hal_cli);
 
   bool IsSuspended() const;
   void CleanUp();
 
+  bool IsConfirm(const int x, const int y) {
+    return renderer_->IsInConfirm(x, y);
+  }
+
+  bool IsCancel(const int x, const int y) {
+    return renderer_->IsInCancel(x, y);
+  }
+
+  // tell if grace period has passed
+  bool IsReadyForUserInput() const;
+
  private:
-  /** create a frame, and render it on the vnc/webRTC client
+  bool IsUserInput(const FsmInput fsm_input) {
+    return fsm_input == FsmInput::kUserEvent;
+  }
+
+  /** create a frame, and render it on the webRTC client
    *
    * note that this does not check host_ctrl_mode_
    */
-  bool RenderDialog(const std::string& msg, const std::string& locale);
+  bool RenderDialog();
 
   // transition actions on each state per input
   // the new state will be save to the state_ at the end of each call
-  void HandleInit(const bool is_user_input, SharedFD hal_cli,
-                  const FsmInput fsm_input, const std::string& additional_info);
+  //
+  // when false is returned, the FSM must terminate
+  // and, no need to let the guest know
+  bool HandleInit(SharedFD hal_cli, const FsmInput fsm_input,
+                  const ConfUiMessage& conf_ui_msg);
 
-  void HandleWaitStop(const bool is_user_input, SharedFD hal_cli,
-                      const FsmInput fsm_input);
+  bool HandleWaitStop(SharedFD hal_cli, const FsmInput fsm_input);
 
-  void HandleInSession(const bool is_user_input, SharedFD hal_cli,
-                       const FsmInput fsm_input);
-
-  bool Kill(SharedFD hal_cli, const std::string& response_msg);
+  bool HandleInSession(SharedFD hal_cli, const FsmInput fsm_input,
+                       const ConfUiMessage& conf_ui_msg);
 
   // report with an error ack to HAL, and reset the FSM
-  void ReportErrorToHal(SharedFD hal_cli, const std::string& msg);
+  bool ReportErrorToHal(SharedFD hal_cli, const std::string& msg);
+
+  void ScheduleToTerminate();
+
+  bool IsInverted() const;
+  bool IsMagnified() const;
 
   const std::string session_id_;
   const std::uint32_t display_num_;
-  // host renderer is shared across sessions
-  ConfUiRenderer& renderer_;
+  std::unique_ptr<ConfUiRenderer> renderer_;
   HostModeCtrl& host_mode_ctrl_;
   ScreenConnectorFrameRenderer& screen_connector_;
 
   // only context to save
-  std::string prompt_;
+  std::string prompt_text_;
   std::string locale_;
+  std::vector<teeui::UIOption> ui_options_;
+  std::vector<std::uint8_t> extra_data_;
+  // the second argument for resultCB of promptUserConfirmation
+  std::vector<std::uint8_t> signed_confirmation_;
+  std::vector<std::uint8_t> message_;
 
-  // effectively, this variables are shared with vnc, webRTC thread
+  std::unique_ptr<Cbor> cbor_;
+
+  // effectively, this variables are shared with webRTC thread
   // the input demuxer will check the confirmation UI mode based on this
   std::atomic<MainLoopState> state_;
   MainLoopState saved_state_;  // for restore/suspend
+  using Clock = std::chrono::steady_clock;
+  using TimePoint = std::chrono::time_point<Clock>;
+  std::unique_ptr<TimePoint> start_time_;
 };
 }  // end of namespace confui
 }  // end of namespace cuttlefish
diff --git a/host/libs/confui/sign.cc b/host/libs/confui/sign.cc
new file mode 100644
index 0000000..2c454e5
--- /dev/null
+++ b/host/libs/confui/sign.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/confui/sign.h"
+
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+
+#include "common/libs/confui/confui.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/security/confui_sign.h"
+#include "host/commands/kernel_log_monitor/utils.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/confui/sign_utils.h"
+
+namespace cuttlefish {
+namespace confui {
+namespace {
+std::string GetSecureEnvSocketPath() {
+  auto config = cuttlefish::CuttlefishConfig::Get();
+  CHECK(config) << "Config must not be null";
+  auto instance = config->ForDefaultInstance();
+  return instance.PerInstanceInternalPath("confui_sign.sock");
+}
+
+/**
+ * the secure_env signing server may be on slightly later than
+ * confirmation UI host/webRTC process.
+ */
+SharedFD ConnectToSecureEnv() {
+  auto socket_path = GetSecureEnvSocketPath();
+  SharedFD socket_to_secure_env =
+      SharedFD::SocketLocalClient(socket_path, false, SOCK_STREAM);
+  return socket_to_secure_env;
+}
+}  // end of namespace
+
+class HMacImplementation {
+ public:
+  static std::optional<support::hmac_t> hmac256(
+      const support::auth_token_key_t& key,
+      std::initializer_list<support::ByteBufferProxy> buffers);
+};
+
+std::optional<support::hmac_t> HMacImplementation::hmac256(
+    const support::auth_token_key_t& key,
+    std::initializer_list<support::ByteBufferProxy> buffers) {
+  HMAC_CTX hmacCtx;
+  HMAC_CTX_init(&hmacCtx);
+  if (!HMAC_Init_ex(&hmacCtx, key.data(), key.size(), EVP_sha256(), nullptr)) {
+    return {};
+  }
+  for (auto& buffer : buffers) {
+    if (!HMAC_Update(&hmacCtx, buffer.data(), buffer.size())) {
+      return {};
+    }
+  }
+  support::hmac_t result;
+  if (!HMAC_Final(&hmacCtx, result.data(), nullptr)) {
+    return {};
+  }
+  return result;
+}
+
+/**
+ * The test key is 32byte word with all bytes set to TestKeyBits::BYTE.
+ */
+enum class TestKeyBits : uint8_t {
+  BYTE = 165 /* 0xA5 */,
+};
+
+std::optional<std::vector<std::uint8_t>> TestSign(
+    const std::vector<std::uint8_t>& message) {
+  // the same as userConfirm()
+  using namespace support;
+  auth_token_key_t key;
+  key.fill(static_cast<std::uint8_t>(TestKeyBits::BYTE));
+  using HMacer = HMacImplementation;
+  auto confirm_signed_opt =
+      HMacer::hmac256(key, {"confirmation token", message});
+  if (!confirm_signed_opt) {
+    return std::nullopt;
+  }
+  auto confirm_signed = confirm_signed_opt.value();
+  return {
+      std::vector<std::uint8_t>(confirm_signed.begin(), confirm_signed.end())};
+}
+
+std::optional<std::vector<std::uint8_t>> Sign(
+    const std::vector<std::uint8_t>& message) {
+  SharedFD socket_to_secure_env = ConnectToSecureEnv();
+  if (!socket_to_secure_env->IsOpen()) {
+    ConfUiLog(ERROR) << "Failed to connect to secure_env signing server.";
+    return std::nullopt;
+  }
+  ConfUiSignRequester sign_client(socket_to_secure_env);
+  // request signature
+  sign_client.Request(message);
+  auto response_opt = sign_client.Receive();
+  if (!response_opt) {
+    ConfUiLog(ERROR) << "Received nullopt";
+    return std::nullopt;
+  }
+  // respond should be either error code or the signature
+  auto response = std::move(response_opt.value());
+  if (response.error_ != SignMessageError::kOk) {
+    ConfUiLog(ERROR) << "Response was received with non-OK error code";
+    return std::nullopt;
+  }
+  return {response.payload_};
+}
+}  // namespace confui
+}  // end of namespace cuttlefish
diff --git a/host/libs/confui/sign.h b/host/libs/confui/sign.h
new file mode 100644
index 0000000..234df8d
--- /dev/null
+++ b/host/libs/confui/sign.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+namespace cuttlefish {
+namespace confui {
+
+// sign with the local test key
+std::optional<std::vector<std::uint8_t>> TestSign(
+    const std::vector<std::uint8_t>& message);
+
+// sign with secure_env
+std::optional<std::vector<std::uint8_t>> Sign(
+    const std::vector<std::uint8_t>& message);
+
+}  // namespace confui
+}  // end of namespace cuttlefish
diff --git a/host/libs/confui/sign_utils.h b/host/libs/confui/sign_utils.h
new file mode 100644
index 0000000..929f32a
--- /dev/null
+++ b/host/libs/confui/sign_utils.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+
+namespace cuttlefish {
+namespace confui {
+namespace support {
+using auth_token_key_t = std::array<std::uint8_t, 32>;
+using hmac_t = auth_token_key_t;
+
+template <typename T>
+auto bytes_cast(const T& v) -> const uint8_t (&)[sizeof(T)] {
+  return *reinterpret_cast<const uint8_t(*)[sizeof(T)]>(&v);
+}
+template <typename T>
+auto bytes_cast(T& v) -> uint8_t (&)[sizeof(T)] {
+  return *reinterpret_cast<uint8_t(*)[sizeof(T)]>(&v);
+}
+
+template <typename IntType, uint32_t byteOrder>
+struct choose_hton;
+
+template <typename IntType>
+struct choose_hton<IntType, __ORDER_LITTLE_ENDIAN__> {
+  inline static IntType hton(const IntType& value) {
+    IntType result = {};
+    const unsigned char* inbytes =
+        reinterpret_cast<const unsigned char*>(&value);
+    unsigned char* outbytes = reinterpret_cast<unsigned char*>(&result);
+    for (int i = sizeof(IntType) - 1; i >= 0; --i) {
+      *(outbytes++) = inbytes[i];
+    }
+    return result;
+  }
+};
+
+template <typename IntType>
+struct choose_hton<IntType, __ORDER_BIG_ENDIAN__> {
+  inline static IntType hton(const IntType& value) { return value; }
+};
+
+template <typename IntType>
+inline IntType hton(const IntType& value) {
+  return choose_hton<IntType, __BYTE_ORDER__>::hton(value);
+}
+
+class ByteBufferProxy {
+  template <typename T>
+  struct has_data {
+    template <typename U>
+    static int f(const U*, const void*) {
+      return 0;
+    }
+    template <typename U>
+    static int* f(const U* u, decltype(u->data())) {
+      return nullptr;
+    }
+    static constexpr bool value =
+        std::is_pointer<decltype(f((T*)nullptr, ""))>::value;
+  };
+
+ public:
+  template <typename T>
+  ByteBufferProxy(const T& buffer, decltype(buffer.data()) = nullptr)
+      : data_(reinterpret_cast<const uint8_t*>(buffer.data())),
+        size_(buffer.size()) {
+    static_assert(sizeof(decltype(*buffer.data())) == 1, "elements to large");
+  }
+
+  // this overload kicks in for types that have .c_str() but not .data(), such
+  // as hidl_string. std::string has both so we need to explicitly disable this
+  // overload if .data() is present.
+  template <typename T>
+  ByteBufferProxy(
+      const T& buffer,
+      std::enable_if_t<!has_data<T>::value, decltype(buffer.c_str())> = nullptr)
+      : data_(reinterpret_cast<const uint8_t*>(buffer.c_str())),
+        size_(buffer.size()) {
+    static_assert(sizeof(decltype(*buffer.c_str())) == 1, "elements to large");
+  }
+
+  template <size_t size>
+  ByteBufferProxy(const char (&buffer)[size])
+      : data_(reinterpret_cast<const uint8_t*>(buffer)), size_(size - 1) {
+    static_assert(size > 0, "even an empty string must be 0-terminated");
+  }
+
+  template <size_t size>
+  ByteBufferProxy(const uint8_t (&buffer)[size]) : data_(buffer), size_(size) {}
+
+  ByteBufferProxy() : data_(nullptr), size_(0) {}
+
+  const uint8_t* data() const { return data_; }
+  size_t size() const { return size_; }
+
+  const uint8_t* begin() const { return data_; }
+  const uint8_t* end() const { return data_ + size_; }
+
+ private:
+  const uint8_t* data_;
+  size_t size_;
+};
+
+// copied from:
+// hardware/interface/confirmationui/support/include/android/hardware/confirmationui/support/confirmationui_utils.h
+
+}  // end of namespace support
+}  // end of namespace confui
+}  // end of namespace cuttlefish
diff --git a/host/libs/graphics_detector/Android.bp b/host/libs/graphics_detector/Android.bp
index bd6a55e..679825a 100644
--- a/host/libs/graphics_detector/Android.bp
+++ b/host/libs/graphics_detector/Android.bp
@@ -36,6 +36,7 @@
     ],
     header_libs: [
         "egl_headers",
+        "gl_headers",
         "vulkan_headers",
     ],
     shared_libs: [
@@ -44,3 +45,19 @@
     ],
     defaults: ["cuttlefish_host"],
 }
+
+cc_binary {
+    name: "detect_graphics",
+    srcs: [
+        "detect_graphics.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    static_libs: [
+        "libcuttlefish_graphics_detector",
+        "libgflags",
+    ],
+    defaults: ["cuttlefish_host"],
+}
diff --git a/host/libs/graphics_detector/detect_graphics.cpp b/host/libs/graphics_detector/detect_graphics.cpp
new file mode 100644
index 0000000..6032520
--- /dev/null
+++ b/host/libs/graphics_detector/detect_graphics.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include <gflags/gflags.h>
+
+#include "android-base/logging.h"
+#include "host/libs/graphics_detector/graphics_detector.h"
+
+int main(int argc, char* argv[]) {
+  ::android::base::InitLogging(argv, android::base::StdioLogger);
+  ::android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+  ::gflags::ParseCommandLineFlags(&argc, &argv, true);
+  LOG(INFO) << cuttlefish::GetGraphicsAvailabilityWithSubprocessCheck();
+}
\ No newline at end of file
diff --git a/host/libs/graphics_detector/graphics_detector.cpp b/host/libs/graphics_detector/graphics_detector.cpp
index 367e637..5776c41 100644
--- a/host/libs/graphics_detector/graphics_detector.cpp
+++ b/host/libs/graphics_detector/graphics_detector.cpp
@@ -21,6 +21,7 @@
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
+#include <GLES2/gl2.h>
 #include <android-base/logging.h>
 #include <android-base/strings.h>
 #include <dlfcn.h>
@@ -57,50 +58,50 @@
 void PopulateGlAvailability(GraphicsAvailability* availability) {
   ManagedLibrary gl_lib(dlopen(kGlLib, RTLD_NOW | RTLD_LOCAL));
   if (!gl_lib) {
-    LOG(VERBOSE) << "Failed to dlopen " << kGlLib << ".";
+    LOG(DEBUG) << "Failed to dlopen " << kGlLib << ".";
     return;
   }
-  LOG(VERBOSE) << "Loaded " << kGlLib << ".";
+  LOG(DEBUG) << "Loaded " << kGlLib << ".";
   availability->has_gl = true;
 }
 
 void PopulateGles1Availability(GraphicsAvailability* availability) {
   ManagedLibrary gles1_lib(dlopen(kGles1Lib, RTLD_NOW | RTLD_LOCAL));
   if (!gles1_lib) {
-    LOG(VERBOSE) << "Failed to dlopen " << kGles1Lib << ".";
+    LOG(DEBUG) << "Failed to dlopen " << kGles1Lib << ".";
     return;
   }
-  LOG(VERBOSE) << "Loaded " << kGles1Lib << ".";
+  LOG(DEBUG) << "Loaded " << kGles1Lib << ".";
   availability->has_gles1 = true;
 }
 
 void PopulateGles2Availability(GraphicsAvailability* availability) {
   ManagedLibrary gles2_lib(dlopen(kGles2Lib, RTLD_NOW | RTLD_LOCAL));
   if (!gles2_lib) {
-    LOG(VERBOSE) << "Failed to dlopen " << kGles2Lib << ".";
+    LOG(DEBUG) << "Failed to dlopen " << kGles2Lib << ".";
     return;
   }
-  LOG(VERBOSE) << "Loaded " << kGles2Lib << ".";
+  LOG(DEBUG) << "Loaded " << kGles2Lib << ".";
   availability->has_gles2 = true;
 }
 
 void PopulateEglAvailability(GraphicsAvailability* availability) {
   ManagedLibrary egllib(dlopen(kEglLib, RTLD_NOW | RTLD_LOCAL));
   if (!egllib) {
-    LOG(VERBOSE) << "Failed to dlopen " << kEglLib << ".";
+    LOG(DEBUG) << "Failed to dlopen " << kEglLib << ".";
     return;
   }
-  LOG(VERBOSE) << "Loaded " << kEglLib << ".";
+  LOG(DEBUG) << "Loaded " << kEglLib << ".";
   availability->has_egl = true;
 
   PFNEGLGETPROCADDRESSPROC eglGetProcAddress =
       reinterpret_cast<PFNEGLGETPROCADDRESSPROC>(
           dlsym(egllib.get(), "eglGetProcAddress"));
   if (eglGetProcAddress == nullptr) {
-    LOG(VERBOSE) << "Failed to find function eglGetProcAddress.";
+    LOG(DEBUG) << "Failed to find function eglGetProcAddress.";
     return;
   }
-  LOG(VERBOSE) << "Loaded eglGetProcAddress.";
+  LOG(DEBUG) << "Loaded eglGetProcAddress.";
 
   // Some implementations have it so that eglGetProcAddress is only for
   // loading EXT functions.
@@ -115,155 +116,127 @@
   PFNEGLGETERRORPROC eglGetError =
     reinterpret_cast<PFNEGLGETERRORPROC>(EglLoadFunction("eglGetError"));
   if (eglGetError == nullptr) {
-    LOG(VERBOSE) << "Failed to find function eglGetError.";
+    LOG(DEBUG) << "Failed to find function eglGetError.";
     return;
   }
-  LOG(VERBOSE) << "Loaded eglGetError.";
+  LOG(DEBUG) << "Loaded eglGetError.";
 
   PFNEGLGETDISPLAYPROC eglGetDisplay =
     reinterpret_cast<PFNEGLGETDISPLAYPROC>(EglLoadFunction("eglGetDisplay"));
   if (eglGetDisplay == nullptr) {
-    LOG(VERBOSE) << "Failed to find function eglGetDisplay.";
+    LOG(DEBUG) << "Failed to find function eglGetDisplay.";
     return;
   }
-  LOG(VERBOSE) << "Loaded eglGetDisplay.";
-
-  EGLDisplay default_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-  if (default_display == EGL_NO_DISPLAY) {
-    LOG(VERBOSE) << "Failed to get default display. " << eglGetError();
-    return;
-  }
-  LOG(VERBOSE) << "Found default display.";
-  availability->has_egl_default_display = true;
-
-  PFNEGLINITIALIZEPROC eglInitialize =
-    reinterpret_cast<PFNEGLINITIALIZEPROC>(EglLoadFunction("eglInitialize"));
-  if (eglInitialize == nullptr) {
-    LOG(VERBOSE) << "Failed to find function eglQueryString";
-    return;
-  }
-
-  EGLint client_version_major = 0;
-  EGLint client_version_minor = 0;
-  if (eglInitialize(default_display,
-                    &client_version_major,
-                    &client_version_minor) != EGL_TRUE) {
-    LOG(VERBOSE) << "Failed to initialize default display.";
-    return;
-  }
-  LOG(VERBOSE) << "Initialized default display.";
+  LOG(DEBUG) << "Loaded eglGetDisplay.";
 
   PFNEGLQUERYSTRINGPROC eglQueryString =
     reinterpret_cast<PFNEGLQUERYSTRINGPROC>(EglLoadFunction("eglQueryString"));
   if (eglQueryString == nullptr) {
-    LOG(VERBOSE) << "Failed to find function eglQueryString";
+    LOG(DEBUG) << "Failed to find function eglQueryString";
     return;
   }
-  LOG(VERBOSE) << "Loaded eglQueryString.";
+  LOG(DEBUG) << "Loaded eglQueryString.";
 
-  std::string client_extensions;
-  if (client_version_major >= 1 && client_version_minor >= 5) {
-    client_extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
-  }
-  availability->egl_client_extensions = client_extensions;
-
-  EGLDisplay display = EGL_NO_DISPLAY;
-
-  if (client_extensions.find("EGL_EXT_platform_base") != std::string::npos) {
-    LOG(VERBOSE) << "Client extension EGL_EXT_platform_base is supported.";
+  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+  if (display != EGL_NO_DISPLAY) {
+    LOG(DEBUG) << "Found default display.";
+  } else {
+    LOG(DEBUG) << "Failed to get default display. " << eglGetError()
+                 << ". Attempting to get surfaceless display via "
+                 << "eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA)";
 
     PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT =
       reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
         EglLoadFunction("eglGetPlatformDisplayEXT"));
     if (eglGetPlatformDisplayEXT == nullptr) {
-      LOG(VERBOSE) << "Failed to find function eglGetPlatformDisplayEXT";
-      return;
+      LOG(DEBUG) << "Failed to find function eglGetPlatformDisplayEXT";
+    } else {
+      display = eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA,
+                                         EGL_DEFAULT_DISPLAY, NULL);
     }
+  }
 
-    display =
-      eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA,
-                               EGL_DEFAULT_DISPLAY,
-                               NULL);
-  } else {
-    LOG(VERBOSE) << "Failed to find client extension EGL_EXT_platform_base.";
-  }
   if (display == EGL_NO_DISPLAY) {
-    LOG(VERBOSE) << "Failed to get EGL_PLATFORM_SURFACELESS_MESA display..."
-                 << "failing back to EGL_DEFAULT_DISPLAY display.";
-    display = default_display;
-  }
-  if (display == EGL_NO_DISPLAY) {
-    LOG(VERBOSE) << "Failed to find display.";
+    LOG(DEBUG) << "Failed to find display.";
     return;
   }
 
+  PFNEGLINITIALIZEPROC eglInitialize =
+      reinterpret_cast<PFNEGLINITIALIZEPROC>(EglLoadFunction("eglInitialize"));
+  if (eglInitialize == nullptr) {
+    LOG(DEBUG) << "Failed to find function eglQueryString";
+    return;
+  }
+
+  EGLint client_version_major = 0;
+  EGLint client_version_minor = 0;
   if (eglInitialize(display,
                     &client_version_major,
                     &client_version_minor) != EGL_TRUE) {
-    LOG(VERBOSE) << "Failed to initialize surfaceless display.";
+    LOG(DEBUG) << "Failed to initialize display.";
     return;
   }
-  LOG(VERBOSE) << "Initialized surfaceless display.";
+  LOG(DEBUG) << "Initialized display.";
 
   const std::string version_string = eglQueryString(display, EGL_VERSION);
   if (version_string.empty()) {
-    LOG(VERBOSE) << "Failed to query client version.";
+    LOG(DEBUG) << "Failed to query client version.";
     return;
   }
-  LOG(VERBOSE) << "Found version: " << version_string;
+  LOG(DEBUG) << "Found version: " << version_string;
   availability->egl_version = version_string;
 
   const std::string vendor_string = eglQueryString(display, EGL_VENDOR);
   if (vendor_string.empty()) {
-    LOG(VERBOSE) << "Failed to query vendor.";
+    LOG(DEBUG) << "Failed to query vendor.";
     return;
   }
-  LOG(VERBOSE) << "Found vendor: " << vendor_string;
+  LOG(DEBUG) << "Found vendor: " << vendor_string;
   availability->egl_vendor = vendor_string;
 
   const std::string extensions_string = eglQueryString(display, EGL_EXTENSIONS);
   if (extensions_string.empty()) {
-    LOG(VERBOSE) << "Failed to query extensions.";
+    LOG(DEBUG) << "Failed to query extensions.";
     return;
   }
-  LOG(VERBOSE) << "Found extensions: " << extensions_string;
+  LOG(DEBUG) << "Found extensions: " << extensions_string;
   availability->egl_extensions = extensions_string;
 
   if (extensions_string.find(kSurfacelessContextExt) == std::string::npos) {
-    LOG(VERBOSE) << "Failed to find extension EGL_KHR_surfaceless_context.";
+    LOG(DEBUG) << "Failed to find extension EGL_KHR_surfaceless_context.";
     return;
   }
 
   const std::string display_apis_string = eglQueryString(display,
                                                          EGL_CLIENT_APIS);
   if (display_apis_string.empty()) {
-    LOG(VERBOSE) << "Failed to query display apis.";
+    LOG(DEBUG) << "Failed to query display apis.";
     return;
   }
-  LOG(VERBOSE) << "Found display apis: " << display_apis_string;
+  LOG(DEBUG) << "Found display apis: " << display_apis_string;
 
   PFNEGLBINDAPIPROC eglBindAPI =
     reinterpret_cast<PFNEGLBINDAPIPROC>(EglLoadFunction("eglBindAPI"));
   if (eglBindAPI == nullptr) {
-    LOG(VERBOSE) << "Failed to find function eglBindAPI";
+    LOG(DEBUG) << "Failed to find function eglBindAPI";
     return;
   }
-  LOG(VERBOSE) << "Loaded eglBindAPI.";
+  LOG(DEBUG) << "Loaded eglBindAPI.";
 
   if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) {
-    LOG(VERBOSE) << "Failed to bind GLES API.";
+    LOG(DEBUG) << "Failed to bind GLES API.";
     return;
   }
-  LOG(VERBOSE) << "Bound GLES API.";
+  LOG(DEBUG) << "Bound GLES API.";
 
   PFNEGLCHOOSECONFIGPROC eglChooseConfig =
     reinterpret_cast<PFNEGLCHOOSECONFIGPROC>(
       EglLoadFunction("eglChooseConfig"));
   if (eglChooseConfig == nullptr) {
-    LOG(VERBOSE) << "Failed to find function eglChooseConfig";
+    LOG(DEBUG) << "Failed to find function eglChooseConfig";
     return;
   }
-  LOG(VERBOSE) << "Loaded eglChooseConfig.";
+  LOG(DEBUG) << "Loaded eglChooseConfig.";
 
   const EGLint framebuffer_config_attributes[] = {
     EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
@@ -282,28 +255,28 @@
                       &framebuffer_config,
                       1,
                       &num_framebuffer_configs) != EGL_TRUE) {
-    LOG(VERBOSE) << "Failed to find matching framebuffer config.";
+    LOG(DEBUG) << "Failed to find matching framebuffer config.";
     return;
   }
-  LOG(VERBOSE) << "Found matching framebuffer config.";
+  LOG(DEBUG) << "Found matching framebuffer config.";
 
   PFNEGLCREATECONTEXTPROC eglCreateContext =
     reinterpret_cast<PFNEGLCREATECONTEXTPROC>(
       EglLoadFunction("eglCreateContext"));
   if (eglCreateContext == nullptr) {
-    LOG(VERBOSE) << "Failed to find function eglCreateContext";
+    LOG(DEBUG) << "Failed to find function eglCreateContext";
     return;
   }
-  LOG(VERBOSE) << "Loaded eglCreateContext.";
+  LOG(DEBUG) << "Loaded eglCreateContext.";
 
   PFNEGLDESTROYCONTEXTPROC eglDestroyContext =
     reinterpret_cast<PFNEGLDESTROYCONTEXTPROC>(
       EglLoadFunction("eglDestroyContext"));
   if (eglDestroyContext == nullptr) {
-    LOG(VERBOSE) << "Failed to find function eglDestroyContext";
+    LOG(DEBUG) << "Failed to find function eglDestroyContext";
     return;
   }
-  LOG(VERBOSE) << "Loaded eglDestroyContext.";
+  LOG(DEBUG) << "Loaded eglDestroyContext.";
 
   const EGLint context_attributes[] = {
     EGL_CONTEXT_CLIENT_VERSION, 2,
@@ -315,38 +288,77 @@
                                         EGL_NO_CONTEXT,
                                         context_attributes);
   if (context == EGL_NO_CONTEXT) {
-    LOG(VERBOSE) << "Failed to create EGL context.";
+    LOG(DEBUG) << "Failed to create EGL context.";
     return;
   }
-  LOG(VERBOSE) << "Created EGL context.";
+  LOG(DEBUG) << "Created EGL context.";
   Closer context_closer([&]() { eglDestroyContext(display, context); });
 
   PFNEGLMAKECURRENTPROC eglMakeCurrent =
     reinterpret_cast<PFNEGLMAKECURRENTPROC>(EglLoadFunction("eglMakeCurrent"));
   if (eglMakeCurrent == nullptr) {
-    LOG(VERBOSE) << "Failed to find function eglMakeCurrent";
+    LOG(DEBUG) << "Failed to find function eglMakeCurrent";
     return;
   }
-  LOG(VERBOSE) << "Loaded eglMakeCurrent.";
+  LOG(DEBUG) << "Loaded eglMakeCurrent.";
 
   if (eglMakeCurrent(display,
                      EGL_NO_SURFACE,
                      EGL_NO_SURFACE,
                      context) != EGL_TRUE) {
-    LOG(VERBOSE) << "Failed to make EGL context current.";
+    LOG(DEBUG) << "Failed to make EGL context current.";
     return;
   }
-  LOG(VERBOSE) << "Make EGL context current.";
-  availability->has_egl_surfaceless_with_gles = true;
+  LOG(DEBUG) << "Make EGL context current.";
+  availability->can_init_gles2_on_egl_surfaceless = true;
+
+  PFNGLGETSTRINGPROC glGetString =
+      reinterpret_cast<PFNGLGETSTRINGPROC>(eglGetProcAddress("glGetString"));
+
+  const GLubyte* gles2_vendor = glGetString(GL_VENDOR);
+  if (gles2_vendor == nullptr) {
+    LOG(DEBUG) << "Failed to query GLES2 vendor.";
+    return;
+  }
+  const std::string gles2_vendor_string((const char*)gles2_vendor);
+  LOG(DEBUG) << "Found GLES2 vendor: " << gles2_vendor_string;
+  availability->gles2_vendor = gles2_vendor_string;
+
+  const GLubyte* gles2_version = glGetString(GL_VERSION);
+  if (gles2_version == nullptr) {
+    LOG(DEBUG) << "Failed to query GLES2 vendor.";
+    return;
+  }
+  const std::string gles2_version_string((const char*)gles2_version);
+  LOG(DEBUG) << "Found GLES2 version: " << gles2_version_string;
+  availability->gles2_version = gles2_version_string;
+
+  const GLubyte* gles2_renderer = glGetString(GL_RENDERER);
+  if (gles2_renderer == nullptr) {
+    LOG(DEBUG) << "Failed to query GLES2 renderer.";
+    return;
+  }
+  const std::string gles2_renderer_string((const char*)gles2_renderer);
+  LOG(DEBUG) << "Found GLES2 renderer: " << gles2_renderer_string;
+  availability->gles2_renderer = gles2_renderer_string;
+
+  const GLubyte* gles2_extensions = glGetString(GL_EXTENSIONS);
+  if (gles2_extensions == nullptr) {
+    LOG(DEBUG) << "Failed to query GLES2 extensions.";
+    return;
+  }
+  const std::string gles2_extensions_string((const char*)gles2_extensions);
+  LOG(DEBUG) << "Found GLES2 extensions: " << gles2_extensions_string;
+  availability->gles2_extensions = gles2_extensions_string;
 }
 
 void PopulateVulkanAvailability(GraphicsAvailability* availability) {
   ManagedLibrary vklib(dlopen(kVulkanLib, RTLD_NOW | RTLD_LOCAL));
   if (!vklib) {
-    LOG(VERBOSE) << "Failed to dlopen " << kVulkanLib << ".";
+    LOG(DEBUG) << "Failed to dlopen " << kVulkanLib << ".";
     return;
   }
-  LOG(VERBOSE) << "Loaded " << kVulkanLib << ".";
+  LOG(DEBUG) << "Loaded " << kVulkanLib << ".";
   availability->has_vulkan = true;
 
   uint32_t instance_version = 0;
@@ -355,7 +367,7 @@
       reinterpret_cast<PFN_vkGetInstanceProcAddr>(
           dlsym(vklib.get(), "vkGetInstanceProcAddr"));
   if (vkGetInstanceProcAddr == nullptr) {
-    LOG(VERBOSE) << "Failed to find symbol vkGetInstanceProcAddr.";
+    LOG(DEBUG) << "Failed to find symbol vkGetInstanceProcAddr.";
     return;
   }
 
@@ -371,7 +383,7 @@
     reinterpret_cast<PFN_vkCreateInstance>(
       vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"));
   if (vkCreateInstance == nullptr) {
-    LOG(VERBOSE) << "Failed to get function vkCreateInstance.";
+    LOG(DEBUG) << "Failed to get function vkCreateInstance.";
     return;
   }
 
@@ -398,25 +410,25 @@
   VkResult result = vkCreateInstance(&instance_create_info, nullptr, &instance);
   if (result != VK_SUCCESS) {
     if (result == VK_ERROR_OUT_OF_HOST_MEMORY) {
-      LOG(VERBOSE) << "Failed to create Vulkan instance: "
+      LOG(DEBUG) << "Failed to create Vulkan instance: "
                    << "VK_ERROR_OUT_OF_HOST_MEMORY.";
     } else if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY) {
-      LOG(VERBOSE) << "Failed to create Vulkan instance: "
+      LOG(DEBUG) << "Failed to create Vulkan instance: "
                    << "VK_ERROR_OUT_OF_DEVICE_MEMORY.";
     } else if (result == VK_ERROR_INITIALIZATION_FAILED) {
-      LOG(VERBOSE) << "Failed to create Vulkan instance: "
+      LOG(DEBUG) << "Failed to create Vulkan instance: "
                    << "VK_ERROR_INITIALIZATION_FAILED.";
     } else if (result == VK_ERROR_LAYER_NOT_PRESENT) {
-      LOG(VERBOSE) << "Failed to create Vulkan instance: "
+      LOG(DEBUG) << "Failed to create Vulkan instance: "
                    << "VK_ERROR_LAYER_NOT_PRESENT.";
     } else if (result == VK_ERROR_EXTENSION_NOT_PRESENT) {
-      LOG(VERBOSE) << "Failed to create Vulkan instance: "
+      LOG(DEBUG) << "Failed to create Vulkan instance: "
                    << "VK_ERROR_EXTENSION_NOT_PRESENT.";
     } else if (result == VK_ERROR_INCOMPATIBLE_DRIVER) {
-      LOG(VERBOSE) << "Failed to create Vulkan instance: "
+      LOG(DEBUG) << "Failed to create Vulkan instance: "
                    << "VK_ERROR_INCOMPATIBLE_DRIVER.";
     } else {
-      LOG(VERBOSE) << "Failed to create Vulkan instance.";
+      LOG(DEBUG) << "Failed to create Vulkan instance.";
     }
     return;
   }
@@ -425,7 +437,7 @@
     reinterpret_cast<PFN_vkDestroyInstance>(
       vkGetInstanceProcAddr(instance, "vkDestroyInstance"));
   if (vkDestroyInstance == nullptr) {
-    LOG(VERBOSE) << "Failed to get function vkDestroyInstance.";
+    LOG(DEBUG) << "Failed to get function vkDestroyInstance.";
     return;
   }
 
@@ -435,7 +447,7 @@
     reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(
       vkGetInstanceProcAddr(instance, "vkEnumeratePhysicalDevices"));
   if (vkEnumeratePhysicalDevices == nullptr) {
-    LOG(VERBOSE) << "Failed to "
+    LOG(DEBUG) << "Failed to "
                  << "vkGetInstanceProcAddr(vkEnumeratePhysicalDevices).";
     return;
   }
@@ -444,7 +456,7 @@
     reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(
       vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties"));
   if (vkGetPhysicalDeviceProperties == nullptr) {
-    LOG(VERBOSE) << "Failed to "
+    LOG(DEBUG) << "Failed to "
                  << "vkGetInstanceProcAddr(vkGetPhysicalDeviceProperties).";
     return;
   }
@@ -453,7 +465,7 @@
     reinterpret_cast<PFN_vkEnumerateDeviceExtensionProperties>(
       vkGetInstanceProcAddr(instance, "vkEnumerateDeviceExtensionProperties"));
   if (vkEnumerateDeviceExtensionProperties == nullptr) {
-    LOG(VERBOSE) << "Failed to "
+    LOG(DEBUG) << "Failed to "
                  << "vkGetInstanceProcAddr("
                  << "vkEnumerateDeviceExtensionProperties"
                  << ").";
@@ -464,32 +476,32 @@
   result = vkEnumeratePhysicalDevices(instance, &device_count, nullptr);
   if (result != VK_SUCCESS) {
     if (result == VK_INCOMPLETE) {
-      LOG(VERBOSE) << "Failed to enumerate physical device count: "
+      LOG(DEBUG) << "Failed to enumerate physical device count: "
                    << "VK_INCOMPLETE";
     } else if (result == VK_ERROR_OUT_OF_HOST_MEMORY) {
-      LOG(VERBOSE) << "Failed to enumerate physical device count: "
+      LOG(DEBUG) << "Failed to enumerate physical device count: "
                    << "VK_ERROR_OUT_OF_HOST_MEMORY";
     } else if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY) {
-      LOG(VERBOSE) << "Failed to enumerate physical device count: "
+      LOG(DEBUG) << "Failed to enumerate physical device count: "
                    << "VK_ERROR_OUT_OF_DEVICE_MEMORY";
     } else if (result == VK_ERROR_INITIALIZATION_FAILED) {
-      LOG(VERBOSE) << "Failed to enumerate physical device count: "
+      LOG(DEBUG) << "Failed to enumerate physical device count: "
                    << "VK_ERROR_INITIALIZATION_FAILED";
     } else {
-      LOG(VERBOSE) << "Failed to enumerate physical device count.";
+      LOG(DEBUG) << "Failed to enumerate physical device count.";
     }
     return;
   }
 
   if (device_count == 0) {
-    LOG(VERBOSE) << "No physical devices present.";
+    LOG(DEBUG) << "No physical devices present.";
     return;
   }
 
   std::vector<VkPhysicalDevice> devices(device_count, VK_NULL_HANDLE);
   result = vkEnumeratePhysicalDevices(instance, &device_count, devices.data());
   if (result != VK_SUCCESS) {
-    LOG(VERBOSE) << "Failed to enumerate physical devices.";
+    LOG(DEBUG) << "Failed to enumerate physical devices.";
     return;
   }
 
@@ -497,6 +509,8 @@
     VkPhysicalDeviceProperties device_properties = {};
     vkGetPhysicalDeviceProperties(device, &device_properties);
 
+    LOG(DEBUG) << "Found physical device: " << device_properties.deviceName;
+
     uint32_t device_extensions_count = 0;
     vkEnumerateDeviceExtensionProperties(device,
                                          nullptr,
@@ -519,6 +533,9 @@
     std::string device_extensions_string =
       android::base::Join(device_extensions_strings, ' ');
 
+    LOG(DEBUG) << "Found physical device extensions: "
+                 << device_extensions_string;
+
     if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
       availability->has_discrete_gpu = true;
       availability->discrete_gpu_device_name = device_properties.deviceName;
@@ -528,6 +545,18 @@
   }
 }
 
+std::string ToLower(const std::string& v) {
+  std::string result = v;
+  std::transform(result.begin(), result.end(), result.begin(),
+                 [](unsigned char c) { return std::tolower(c); });
+  return result;
+}
+
+bool IsLikelySoftwareRenderer(const std::string& renderer) {
+  const std::string lower_renderer = ToLower(renderer);
+  return lower_renderer.find("llvmpipe") != std::string::npos;
+}
+
 GraphicsAvailability GetGraphicsAvailability() {
   GraphicsAvailability availability;
 
@@ -544,7 +573,8 @@
 
 bool ShouldEnableAcceleratedRendering(
     const GraphicsAvailability& availability) {
-  return availability.has_egl && availability.has_egl_surfaceless_with_gles &&
+  return availability.can_init_gles2_on_egl_surfaceless &&
+         !IsLikelySoftwareRenderer(availability.gles2_renderer) &&
          availability.has_discrete_gpu;
 }
 
@@ -560,13 +590,13 @@
   }
   int status;
   if (waitpid(pid, &status, 0) != pid) {
-    PLOG(ERROR) << "Failed to wait for graphics check subprocess";
+    PLOG(DEBUG) << "Failed to wait for graphics check subprocess";
     return GraphicsAvailability{};
   }
   if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
     return GetGraphicsAvailability();
   }
-  LOG(VERBOSE) << "Subprocess for detect_graphics failed with " << status;
+  LOG(DEBUG) << "Subprocess for detect_graphics failed with " << status;
   return GraphicsAvailability{};
 }
 
@@ -575,20 +605,32 @@
   std::ios_base::fmtflags flags_backup(stream.flags());
   stream << std::boolalpha;
   stream << "Graphics Availability:\n";
-  stream << "OpenGL available: " << availability.has_gl << "\n";
-  stream << "OpenGL ES1 available: " << availability.has_gles1 << "\n";
-  stream << "OpenGL ES2 available: " << availability.has_gles2 << "\n";
-  stream << "EGL available: " << availability.has_egl << "\n";
+
+  stream << "\n";
+  stream << "OpenGL lib available: " << availability.has_gl << "\n";
+  stream << "OpenGL ES1 lib available: " << availability.has_gles1 << "\n";
+  stream << "OpenGL ES2 lib available: " << availability.has_gles2 << "\n";
+  stream << "EGL lib available: " << availability.has_egl << "\n";
+  stream << "Vulkan lib available: " << availability.has_vulkan << "\n";
+
+  stream << "\n";
   stream << "EGL client extensions: " << availability.egl_client_extensions
          << "\n";
-  stream << "EGL default display available: "
-         << availability.has_egl_default_display << "\n";
+
+  stream << "\n";
   stream << "EGL display vendor: " << availability.egl_vendor << "\n";
   stream << "EGL display version: " << availability.egl_version << "\n";
   stream << "EGL display extensions: " << availability.egl_extensions << "\n";
-  stream << "EGL surfaceless display with GLES: "
-         << availability.has_egl_surfaceless_with_gles << "\n";
-  stream << "Vulkan available: " << availability.has_vulkan << "\n";
+
+  stream << "GLES2 can init on surfaceless display: "
+         << availability.can_init_gles2_on_egl_surfaceless << "\n";
+  stream << "\n";
+  stream << "GLES2 vendor: " << availability.gles2_vendor << "\n";
+  stream << "GLES2 version: " << availability.gles2_version << "\n";
+  stream << "GLES2 renderer: " << availability.gles2_renderer << "\n";
+  stream << "GLES2 extensions: " << availability.gles2_extensions << "\n";
+
+  stream << "\n";
   stream << "Vulkan discrete GPU detected: " << availability.has_discrete_gpu
          << "\n";
   if (availability.has_discrete_gpu) {
@@ -597,6 +639,11 @@
     stream << "Vulkan discrete GPU device extensions: "
            << availability.discrete_gpu_device_extensions << "\n";
   }
+
+  stream << "\n";
+  stream << "Accelerated rendering supported: "
+         << ShouldEnableAcceleratedRendering(availability);
+
   stream.flags(flags_backup);
   return stream;
 }
diff --git a/host/libs/graphics_detector/graphics_detector.h b/host/libs/graphics_detector/graphics_detector.h
index 83a7068..1bacc3d 100644
--- a/host/libs/graphics_detector/graphics_detector.h
+++ b/host/libs/graphics_detector/graphics_detector.h
@@ -25,13 +25,20 @@
   bool has_gles1 = false;
   bool has_gles2 = false;
   bool has_egl = false;
-  bool has_egl_default_display = false;
+  bool has_vulkan = false;
+
   std::string egl_client_extensions;
+
   std::string egl_version;
   std::string egl_vendor;
   std::string egl_extensions;
-  bool has_egl_surfaceless_with_gles = false;
-  bool has_vulkan = false;
+
+  bool can_init_gles2_on_egl_surfaceless = false;
+  std::string gles2_vendor;
+  std::string gles2_version;
+  std::string gles2_renderer;
+  std::string gles2_extensions;
+
   bool has_discrete_gpu = false;
   std::string discrete_gpu_device_name;
   std::string discrete_gpu_device_extensions;
diff --git a/host/libs/image_aggregator/image_aggregator.cc b/host/libs/image_aggregator/image_aggregator.cc
index b6b412d..49ec27e 100644
--- a/host/libs/image_aggregator/image_aggregator.cc
+++ b/host/libs/image_aggregator/image_aggregator.cc
@@ -31,6 +31,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/strings.h>
 #include <cdisk_spec.pb.h>
 #include <google/protobuf/text_format.h>
 #include <sparse/sparse.h>
@@ -39,6 +40,7 @@
 
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/cf_endian.h"
 #include "common/libs/utils/files.h"
 #include "common/libs/utils/size_utils.h"
 #include "common/libs/utils/subprocess.h"
@@ -48,6 +50,8 @@
 namespace {
 
 constexpr int GPT_NUM_PARTITIONS = 128;
+static const std::string CDISK_MAGIC = "composite_disk\x1d";
+static const std::string QCOW2_MAGIC = "QFI\xfb";
 
 /**
  * Creates a "Protective" MBR Partition Table header. The GUID
@@ -100,32 +104,53 @@
 struct __attribute__((packed)) GptBeginning {
   MasterBootRecord protective_mbr;
   GptHeader header;
-  std::uint8_t header_padding[420];
+  std::uint8_t header_padding[SECTOR_SIZE - sizeof(GptHeader)];
   GptPartitionEntry entries[GPT_NUM_PARTITIONS];
   std::uint8_t partition_alignment[3072];
 };
 
-static_assert(sizeof(GptBeginning) == SECTOR_SIZE * 40);
+static_assert(AlignToPowerOf2(sizeof(GptBeginning), PARTITION_SIZE_SHIFT) ==
+              sizeof(GptBeginning));
 
 struct __attribute__((packed)) GptEnd {
   GptPartitionEntry entries[GPT_NUM_PARTITIONS];
   GptHeader footer;
-  std::uint8_t footer_padding[420];
+  std::uint8_t footer_padding[SECTOR_SIZE - sizeof(GptHeader)];
 };
 
-static_assert(sizeof(GptEnd) == SECTOR_SIZE * 33);
+static_assert(sizeof(GptEnd) % SECTOR_SIZE == 0);
 
 struct PartitionInfo {
   MultipleImagePartition source;
-  std::uint64_t guest_size;
-  std::uint64_t host_size;
+  std::uint64_t size;
   std::uint64_t offset;
+
+  std::uint64_t AlignedSize() const { return AlignToPartitionSize(size); }
 };
 
+struct __attribute__((packed)) QCowHeader {
+  Be32 magic;
+  Be32 version;
+  Be64 backing_file_offset;
+  Be32 backing_file_size;
+  Be32 cluster_bits;
+  Be64 size;
+  Be32 crypt_method;
+  Be32 l1_size;
+  Be64 l1_table_offset;
+  Be64 refcount_table_offset;
+  Be32 refcount_table_clusters;
+  Be32 nb_snapshots;
+  Be64 snapshots_offset;
+};
+
+static_assert(sizeof(QCowHeader) == 72);
+
 /*
- * Returns the file size of `file_path`. If `file_path` is an Android-Sparse
- * file, returns the file size it would have after being converted to a raw
- * file.
+ * Returns the expanded file size of `file_path`. Note that the raw size of
+ * files doesn't match how large they may appear inside a VM.
+ *
+ * Supported types: Composite disk image, Qcows2, Android-Sparse, Raw
  *
  * Android-Sparse is a file format invented by Android that optimizes for
  * chunks of zeroes or repeated data. The Android build system can produce
@@ -133,15 +158,63 @@
  * disk file, as the imag eflashing process also can handle Android-Sparse
  * images.
  */
-std::uint64_t UnsparsedSize(const std::string& file_path) {
-  auto fd = open(file_path.c_str(), O_RDONLY);
-  CHECK(fd >= 0) << "Could not open \"" << file_path << "\""
-                 << strerror(errno);
-  auto sparse = sparse_file_import(fd, /* verbose */ false, /* crc */ false);
-  auto size =
-      sparse ? sparse_file_len(sparse, false, true) : FileSize(file_path);
-  close(fd);
-  return size;
+std::uint64_t ExpandedStorageSize(const std::string& file_path) {
+  android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY));
+  CHECK(fd.get() >= 0) << "Could not open \"" << file_path << "\""
+                       << strerror(errno);
+
+  std::uint64_t file_size = FileSize(file_path);
+
+  // Try to read the disk in a nicely-aligned block size unless the whole file
+  // is smaller.
+  constexpr uint64_t MAGIC_BLOCK_SIZE = 4096;
+  std::string magic(std::min(file_size, MAGIC_BLOCK_SIZE), '\0');
+  if (!android::base::ReadFully(fd, magic.data(), magic.size())) {
+    PLOG(FATAL) << "Fail to read: " << file_path;
+    return 0;
+  }
+  CHECK(lseek(fd, 0, SEEK_SET) != -1)
+      << "Fail to seek(\"" << file_path << "\")" << strerror(errno);
+
+  // Composite disk image
+  if (android::base::StartsWith(magic, CDISK_MAGIC)) {
+    // seek to the beginning of proto message
+    CHECK(lseek(fd, CDISK_MAGIC.size(), SEEK_SET) != -1)
+        << "Fail to seek(\"" << file_path << "\")" << strerror(errno);
+    std::string message;
+    if (!android::base::ReadFdToString(fd, &message)) {
+      PLOG(FATAL) << "Fail to read(cdisk): " << file_path;
+      return 0;
+    }
+    CompositeDisk cdisk;
+    if (!cdisk.ParseFromString(message)) {
+      PLOG(FATAL) << "Fail to parse(cdisk): " << file_path;
+      return 0;
+    }
+    return cdisk.length();
+  }
+
+  // Qcow2 image
+  if (android::base::StartsWith(magic, QCOW2_MAGIC)) {
+    QCowHeader header;
+    if (!android::base::ReadFully(fd, &header, sizeof(QCowHeader))) {
+      PLOG(FATAL) << "Fail to read(qcow2 header): " << file_path;
+      return 0;
+    }
+    return header.size.as_uint64_t();
+  }
+
+  // Android-Sparse
+  if (auto sparse =
+          sparse_file_import(fd, /* verbose */ false, /* crc */ false);
+      sparse) {
+    auto size = sparse_file_len(sparse, false, true);
+    sparse_file_destroy(sparse);
+    return size;
+  }
+
+  // raw image file
+  return file_size;
 }
 
 /*
@@ -200,27 +273,24 @@
   }
 
   void AppendPartition(MultipleImagePartition source) {
-    uint64_t host_size = 0;
+    uint64_t size = 0;
     for (const auto& path : source.image_file_paths) {
-      host_size += UnsparsedSize(path);
+      size += ExpandedStorageSize(path);
     }
-    auto guest_size = AlignToPowerOf2(host_size, PARTITION_SIZE_SHIFT);
-    CHECK(host_size == guest_size || source.read_only)
+    auto aligned_size = AlignToPartitionSize(size);
+    CHECK(size == aligned_size || source.read_only)
         << "read-write partition " << source.label
         << " is not aligned to the size of " << (1 << PARTITION_SIZE_SHIFT);
     partitions_.push_back(PartitionInfo{
         .source = source,
-        .guest_size = guest_size,
-        .host_size = host_size,
+        .size = size,
         .offset = next_disk_offset_,
     });
-    next_disk_offset_ =
-        AlignToPowerOf2(next_disk_offset_ + guest_size, PARTITION_SIZE_SHIFT);
+    next_disk_offset_ = next_disk_offset_ + aligned_size;
   }
 
   std::uint64_t DiskSize() const {
-    std::uint64_t val = next_disk_offset_ + sizeof(GptEnd);
-    return AlignToPowerOf2(val, DISK_SIZE_SHIFT);
+    return AlignToPowerOf2(next_disk_offset_ + sizeof(GptEnd), DISK_SIZE_SHIFT);
   }
 
   /**
@@ -231,41 +301,41 @@
   CompositeDisk MakeCompositeDiskSpec(const std::string& header_file,
                                       const std::string& footer_file) const {
     CompositeDisk disk;
-    disk.set_version(1);
+    disk.set_version(2);
     disk.set_length(DiskSize());
 
     ComponentDisk* header = disk.add_component_disks();
-    header->set_file_path(AbsolutePath(header_file));
+    header->set_file_path(header_file);
     header->set_offset(0);
 
     for (auto& partition : partitions_) {
-      uint64_t host_size = 0;
+      uint64_t size = 0;
       for (const auto& path : partition.source.image_file_paths) {
         ComponentDisk* component = disk.add_component_disks();
-        component->set_file_path(AbsolutePath(path));
-        component->set_offset(partition.offset + host_size);
+        component->set_file_path(path);
+        component->set_offset(partition.offset + size);
         component->set_read_write_capability(
             partition.source.read_only ? ReadWriteCapability::READ_ONLY
                                        : ReadWriteCapability::READ_WRITE);
-        host_size += UnsparsedSize(path);
+        size += ExpandedStorageSize(path);
       }
-      CHECK(partition.host_size == host_size);
-      // When partition's size differs from its size on the host
+      CHECK(partition.size == size);
+      // When partition's aligned size differs from its (unaligned) size
       // reading the disk within the guest os would fail due to the gap.
       // Putting any disk bigger than 4K can fill this gap.
       // Here we reuse the header which is always > 4K.
       // We don't fill the "writable" disk's hole and it should be an error
       // because writes in the guest of can't be reflected to the backing file.
-      if (partition.guest_size != partition.host_size) {
+      if (partition.AlignedSize() != partition.size) {
         ComponentDisk* component = disk.add_component_disks();
-        component->set_file_path(AbsolutePath(header_file));
-        component->set_offset(partition.offset + partition.host_size);
+        component->set_file_path(header_file);
+        component->set_offset(partition.offset + partition.size);
         component->set_read_write_capability(ReadWriteCapability::READ_ONLY);
       }
     }
 
     ComponentDisk* footer = disk.add_component_disks();
-    footer->set_file_path(AbsolutePath(footer_file));
+    footer->set_file_path(footer_file);
     footer->set_offset(next_disk_offset_);
 
     return disk;
@@ -284,19 +354,20 @@
       return {};
     }
     GptBeginning gpt = {
-      .protective_mbr = ProtectiveMbr(DiskSize()),
-      .header = {
-        .signature = {'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'},
-        .revision = {0, 0, 1, 0},
-        .header_size = sizeof(GptHeader),
-        .current_lba = 1,
-        .backup_lba = (next_disk_offset_ + sizeof(GptEnd)) / SECTOR_SIZE - 1,
-        .first_usable_lba = sizeof(GptBeginning) / SECTOR_SIZE,
-        .last_usable_lba = (next_disk_offset_ - SECTOR_SIZE) / SECTOR_SIZE,
-        .partition_entries_lba = 2,
-        .num_partition_entries = GPT_NUM_PARTITIONS,
-        .partition_entry_size = sizeof(GptPartitionEntry),
-      },
+        .protective_mbr = ProtectiveMbr(DiskSize()),
+        .header =
+            {
+                .signature = {'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'},
+                .revision = {0, 0, 1, 0},
+                .header_size = sizeof(GptHeader),
+                .current_lba = 1,
+                .backup_lba = (DiskSize() / SECTOR_SIZE) - 1,
+                .first_usable_lba = sizeof(GptBeginning) / SECTOR_SIZE,
+                .last_usable_lba = (next_disk_offset_ / SECTOR_SIZE) - 1,
+                .partition_entries_lba = 2,
+                .num_partition_entries = GPT_NUM_PARTITIONS,
+                .partition_entry_size = sizeof(GptPartitionEntry),
+            },
     };
     uuid_generate(gpt.header.disk_guid);
     for (std::size_t i = 0; i < partitions_.size(); i++) {
@@ -304,7 +375,7 @@
       gpt.entries[i] = GptPartitionEntry{
           .first_lba = partition.offset / SECTOR_SIZE,
           .last_lba =
-              (partition.offset + partition.guest_size) / SECTOR_SIZE - 1,
+              (partition.offset + partition.AlignedSize()) / SECTOR_SIZE - 1,
       };
       uuid_generate(gpt.entries[i].unique_partition_guid);
       if (uuid_parse(GetPartitionGUID(partition.source),
@@ -330,9 +401,10 @@
    */
   GptEnd End(const GptBeginning& head) const {
     GptEnd gpt;
-    std::memcpy((void*) gpt.entries, (void*) head.entries, 128 * 128);
+    std::memcpy((void*)gpt.entries, (void*)head.entries, sizeof(gpt.entries));
     gpt.footer = head.header;
-    gpt.footer.partition_entries_lba = next_disk_offset_ / SECTOR_SIZE;
+    gpt.footer.partition_entries_lba =
+        (DiskSize() - sizeof(gpt.entries)) / SECTOR_SIZE - 1;
     std::swap(gpt.footer.current_lba, gpt.footer.backup_lba);
     gpt.footer.header_crc32 = 0;
     gpt.footer.header_crc32 =
@@ -350,11 +422,17 @@
   return true;
 }
 
-bool WriteEnd(SharedFD out, const GptEnd& end, std::int64_t padding) {
-  std::string end_str((const char*) &end, sizeof(GptEnd));
-  end_str.resize(end_str.size() + padding, '\0');
-  if (WriteAll(out, end_str) != end_str.size()) {
-    LOG(ERROR) << "Could not write GPT end: " << out->StrError();
+bool WriteEnd(SharedFD out, const GptEnd& end) {
+  auto disk_size = (end.footer.current_lba + 1) * SECTOR_SIZE;
+  auto footer_start = (end.footer.last_usable_lba + 1) * SECTOR_SIZE;
+  auto padding = disk_size - footer_start - sizeof(GptEnd);
+  std::string padding_str(padding, '\0');
+  if (WriteAll(out, padding_str) != padding_str.size()) {
+    LOG(ERROR) << "Could not write GPT end padding: " << out->StrError();
+    return false;
+  }
+  if (WriteAllBinary(out, &end) != sizeof(end)) {
+    LOG(ERROR) << "Could not write GPT end contents: " << out->StrError();
     return false;
   }
   return true;
@@ -435,8 +513,7 @@
                  << "\" to \"" << output_path << "\": " << output->StrError();
     }
     // Handle disk images that are not aligned to PARTITION_SIZE_SHIFT
-    std::uint64_t padding =
-        AlignToPowerOf2(file_size, PARTITION_SIZE_SHIFT) - file_size;
+    std::uint64_t padding = AlignToPartitionSize(file_size) - file_size;
     std::string padding_str;
     padding_str.resize(padding, '\0');
     if (WriteAll(output, padding_str) != padding_str.size()) {
@@ -444,9 +521,7 @@
                  << "\": " << output->StrError();
     }
   }
-  std::uint64_t padding =
-      builder.DiskSize() - ((beginning.header.backup_lba + 1) * SECTOR_SIZE);
-  if (!WriteEnd(output, builder.End(beginning), padding)) {
+  if (!WriteEnd(output, builder.End(beginning))) {
     LOG(FATAL) << "Could not write GPT end to \"" << output_path
                << "\": " << output->StrError();
   }
@@ -479,16 +554,14 @@
                << "\": " << header->StrError();
   }
   auto footer = SharedFD::Creat(footer_file, 0600);
-  std::uint64_t padding =
-      builder.DiskSize() - ((beginning.header.backup_lba + 1) * SECTOR_SIZE);
-  if (!WriteEnd(footer, builder.End(beginning), padding)) {
+  if (!WriteEnd(footer, builder.End(beginning))) {
     LOG(FATAL) << "Could not write GPT end to \"" << footer_file
                << "\": " << footer->StrError();
   }
   auto composite_proto = builder.MakeCompositeDiskSpec(header_file, footer_file);
   std::ofstream composite(output_composite_path.c_str(),
                           std::ios::binary | std::ios::trunc);
-  composite << "composite_disk\x1d";
+  composite << CDISK_MAGIC;
   composite_proto.SerializeToOstream(&composite);
   composite.flush();
 }
@@ -496,13 +569,23 @@
 void CreateQcowOverlay(const std::string& crosvm_path,
                        const std::string& backing_file,
                        const std::string& output_overlay_path) {
-  Command crosvm_qcow2_cmd(crosvm_path);
-  crosvm_qcow2_cmd.AddParameter("create_qcow2");
-  crosvm_qcow2_cmd.AddParameter("--backing_file=", backing_file);
-  crosvm_qcow2_cmd.AddParameter(output_overlay_path);
-  int success = crosvm_qcow2_cmd.Start().Wait();
+  Command cmd(crosvm_path);
+  cmd.AddParameter("create_qcow2");
+  cmd.AddParameter("--backing_file=", backing_file);
+  cmd.AddParameter(output_overlay_path);
+
+  std::string stdout_str;
+  std::string stderr_str;
+  int success =
+      RunWithManagedStdio(std::move(cmd), nullptr, &stdout_str, &stderr_str);
+
   if (success != 0) {
-    LOG(FATAL) << "Unable to run crosvm create_qcow2. Exited with status " << success;
+    LOG(ERROR) << "Failed to run `" << crosvm_path
+               << " create_qcow2 --backing_file=" << backing_file << " "
+               << output_overlay_path << "`";
+    LOG(ERROR) << "stdout:\n###\n" << stdout_str << "\n###";
+    LOG(ERROR) << "stderr:\n###\n" << stderr_str << "\n###";
+    LOG(FATAL) << "Return code: \"" << success << "\"";
   }
 }
 
diff --git a/host/libs/screen_connector/Android.bp b/host/libs/screen_connector/Android.bp
index d4e4002..ae1ef66 100644
--- a/host/libs/screen_connector/Android.bp
+++ b/host/libs/screen_connector/Android.bp
@@ -41,5 +41,5 @@
         "libteeui",
         "libteeui_localization",
     ],
-    defaults: ["cuttlefish_host"],
+    defaults: ["cuttlefish_buildhost_only"],
 }
diff --git a/host/libs/screen_connector/screen_connector.h b/host/libs/screen_connector/screen_connector.h
index ca6b155..49e5253 100644
--- a/host/libs/screen_connector/screen_connector.h
+++ b/host/libs/screen_connector/screen_connector.h
@@ -16,8 +16,6 @@
 
 #pragma once
 
-#include <cassert>
-#include <chrono>
 #include <cstdint>
 #include <functional>
 #include <memory>
@@ -28,15 +26,15 @@
 #include <type_traits>
 
 #include <android-base/logging.h>
-#include "common/libs/concurrency/semaphore.h"
+
 #include "common/libs/confui/confui.h"
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/size_utils.h"
-
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/confui/host_mode_ctrl.h"
 #include "host/libs/confui/host_utils.h"
 #include "host/libs/screen_connector/screen_connector_common.h"
+#include "host/libs/screen_connector/screen_connector_multiplexer.h"
 #include "host/libs/screen_connector/screen_connector_queue.h"
 #include "host/libs/screen_connector/wayland_screen_connector.h"
 
@@ -51,17 +49,15 @@
   static_assert(std::is_base_of<ScreenConnectorFrameInfo, ProcessedFrameType>::value,
                 "ProcessedFrameType should inherit ScreenConnectorFrameInfo");
 
+  using FrameMultiplexer = ScreenConnectorInputMultiplexer<ProcessedFrameType>;
+
   /**
-   * This is the type of the callback function WebRTC/VNC is supposed to provide
+   * This is the type of the callback function WebRTC is supposed to provide
    * ScreenConnector with.
    *
-   * The callback function should be defined so that the two parameters are
-   * given by the callback function caller (e.g. ScreenConnectorSource) and used
-   * to fill out the ProcessedFrameType object, msg.
+   * The callback function is how a raw bytes frame should be processed for
+   * WebRTC
    *
-   * The ProcessedFrameType object is internally created by ScreenConnector,
-   * filled out by the ScreenConnectorSource, and returned via OnNextFrame()
-   * call.
    */
   using GenerateProcessedFrameCallback = std::function<void(
       std::uint32_t /*display_number*/, std::uint32_t /*frame_width*/,
@@ -88,9 +84,9 @@
   virtual ~ScreenConnector() = default;
 
   /**
-   * set the callback function to be eventually used by Wayland/Socket-Based Connectors
+   * set the callback function to be eventually used by Wayland-Based
+   * Connector
    *
-   * @param[in] To tell how ScreenConnectorSource caches the frame & meta info
    */
   void SetCallback(GenerateProcessedFrameCallback&& frame_callback) {
     std::lock_guard<std::mutex> lock(streamer_callback_mutex_);
@@ -115,7 +111,7 @@
                                     processed_frame);
           }
 
-          sc_android_queue_.PushBack(std::move(processed_frame));
+          sc_frame_multiplexer_.PushToAndroidQueue(std::move(processed_frame));
         });
   }
 
@@ -131,44 +127,7 @@
    *
    * NOTE THAT THIS IS THE ONLY CONSUMER OF THE TWO QUEUES
    */
-  ProcessedFrameType OnNextFrame() {
-    on_next_frame_cnt_++;
-    while (true) {
-      ConfUiLog(VERBOSE) << "Streamer waiting Semaphore with host ctrl mode ="
-                         << static_cast<std::uint32_t>(
-                                host_mode_ctrl_.GetMode())
-                         << " and cnd = #" << on_next_frame_cnt_;
-      sc_sem_.SemWait();
-      ConfUiLog(VERBOSE)
-          << "Streamer got Semaphore'ed resources with host ctrl mode ="
-          << static_cast<std::uint32_t>(host_mode_ctrl_.GetMode())
-          << "and cnd = #" << on_next_frame_cnt_;
-      // do something
-      if (!sc_android_queue_.Empty()) {
-        auto mode = host_mode_ctrl_.GetMode();
-        if (mode == HostModeCtrl::ModeType::kAndroidMode) {
-          ConfUiLog(VERBOSE)
-              << "Streamer gets Android frame with host ctrl mode ="
-              << static_cast<std::uint32_t>(mode) << "and cnd = #"
-              << on_next_frame_cnt_;
-          return sc_android_queue_.PopFront();
-        }
-        // AndroidFrameFetchingLoop could have added 1 or 2 frames
-        // before it becomes Conf UI mode.
-        ConfUiLog(VERBOSE)
-            << "Streamer ignores Android frame with host ctrl mode ="
-            << static_cast<std::uint32_t>(mode) << "and cnd = #"
-            << on_next_frame_cnt_;
-        sc_android_queue_.PopFront();
-        continue;
-      }
-      ConfUiLog(VERBOSE) << "Streamer gets Conf UI frame with host ctrl mode = "
-                         << static_cast<std::uint32_t>(
-                                host_mode_ctrl_.GetMode())
-                         << " and cnd = #" << on_next_frame_cnt_;
-      return sc_confui_queue_.PopFront();
-    }
-  }
+  ProcessedFrameType OnNextFrame() { return sc_frame_multiplexer_.Pop(); }
 
   /**
    * ConfUi calls this when it has frames to render
@@ -197,39 +156,32 @@
     callback_from_streamer_(display_number, frame_width, frame_height,
                             frame_stride_bytes, frame_bytes, processed_frame);
     // now add processed_frame to the queue
-    sc_confui_queue_.PushBack(std::move(processed_frame));
+    sc_frame_multiplexer_.PushToConfUiQueue(std::move(processed_frame));
     return true;
   }
 
-  // Let the screen connector know when there are clients connected
-  void ReportClientsConnected(bool have_clients) {
-    // screen connector implementation must implement ReportClientsConnected
-    sc_android_src_->ReportClientsConnected(have_clients);
-    return ;
-  }
-
  protected:
-  template <typename T,
-            typename = std::enable_if_t<
-                std::is_base_of<ScreenConnectorSource, T>::value, void>>
-  ScreenConnector(std::unique_ptr<T>&& impl, HostModeCtrl& host_mode_ctrl)
+  ScreenConnector(std::unique_ptr<WaylandScreenConnector>&& impl,
+                  HostModeCtrl& host_mode_ctrl)
       : sc_android_src_{std::move(impl)},
         host_mode_ctrl_{host_mode_ctrl},
         on_next_frame_cnt_{0},
         render_confui_cnt_{0},
-        sc_android_queue_{sc_sem_},
-        sc_confui_queue_{sc_sem_} {}
+        sc_frame_multiplexer_{host_mode_ctrl_} {}
   ScreenConnector() = delete;
 
  private:
-  // either socket_based or wayland
-  std::unique_ptr<ScreenConnectorSource> sc_android_src_;
+  std::unique_ptr<WaylandScreenConnector> sc_android_src_;
   HostModeCtrl& host_mode_ctrl_;
   unsigned long long int on_next_frame_cnt_;
   unsigned long long int render_confui_cnt_;
-  Semaphore sc_sem_;
-  ScreenConnectorQueue<ProcessedFrameType> sc_android_queue_;
-  ScreenConnectorQueue<ProcessedFrameType> sc_confui_queue_;
+  /**
+   * internally has conf ui & android queues.
+   *
+   * multiplexting the two input queues, so the consumer gets one input
+   * at a time from the right queue
+   */
+  FrameMultiplexer sc_frame_multiplexer_;
   GenerateProcessedFrameCallback callback_from_streamer_;
   std::mutex streamer_callback_mutex_; // mutex to set & read callback_from_streamer_
   std::condition_variable streamer_callback_set_cv_;
diff --git a/host/libs/screen_connector/screen_connector_common.h b/host/libs/screen_connector/screen_connector_common.h
index 1df6d86..32bac76 100644
--- a/host/libs/screen_connector/screen_connector_common.h
+++ b/host/libs/screen_connector/screen_connector_common.h
@@ -18,9 +18,9 @@
 
 #include <cstdint>
 #include <functional>
-#include <type_traits>
 
 #include <android-base/logging.h>
+
 #include "common/libs/utils/size_utils.h"
 #include "host/libs/config/cuttlefish_config.h"
 
@@ -41,17 +41,6 @@
                        std::uint32_t /*frame_stride_bytes*/,  //
                        std::uint8_t* /*frame_pixels*/)>;
 
-class ScreenConnectorSource {
- public:
-  virtual ~ScreenConnectorSource() = default;
-  // Runs the given callback on the next available frame after the given
-  // frame number and returns true if successful.
-  virtual void SetFrameCallback(
-      GenerateProcessedFrameCallbackImpl frame_callback) = 0;
-  virtual void ReportClientsConnected(bool /*have_clients*/) { /* ignore by default */ }
-  ScreenConnectorSource() = default;
-};
-
 struct ScreenConnectorInfo {
   // functions are intended to be inlined
   static constexpr std::uint32_t BytesPerPixel() { return 4; }
@@ -72,12 +61,21 @@
     CHECK_GE(display_configs.size(), display_number);
     return display_configs[display_number].width;
   }
-  static std::uint32_t ScreenStrideBytes(std::uint32_t display_number) {
-    return AlignToPowerOf2(ScreenWidth(display_number) * BytesPerPixel(), 4);
+  static std::uint32_t ComputeScreenStrideBytes(const std::uint32_t w) {
+    return AlignToPowerOf2(w * BytesPerPixel(), 4);
   }
-  static std::uint32_t ScreenSizeInBytes(std::uint32_t display_number) {
-    return ScreenStrideBytes(display_number) * ScreenHeight(display_number);
+  static std::uint32_t ComputeScreenSizeInBytes(const std::uint32_t w,
+                                                const std::uint32_t h) {
+    return ComputeScreenStrideBytes(w) * h;
   }
+  static std::uint32_t ScreenStrideBytes(const std::uint32_t display_number) {
+    return ComputeScreenStrideBytes(ScreenWidth(display_number));
+  }
+  static std::uint32_t ScreenSizeInBytes(const std::uint32_t display_number) {
+    return ComputeScreenStrideBytes(ScreenWidth(display_number)) *
+           ScreenHeight(display_number);
+  }
+
  private:
   static auto ChkAndGetConfig() -> decltype(cuttlefish::CuttlefishConfig::Get()) {
     auto config = cuttlefish::CuttlefishConfig::Get();
diff --git a/host/libs/screen_connector/screen_connector_multiplexer.h b/host/libs/screen_connector/screen_connector_multiplexer.h
new file mode 100644
index 0000000..b620531
--- /dev/null
+++ b/host/libs/screen_connector/screen_connector_multiplexer.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include "common/libs/concurrency/multiplexer.h"
+#include "common/libs/confui/confui.h"
+
+#include "host/libs/confui/host_mode_ctrl.h"
+#include "host/libs/screen_connector/screen_connector_queue.h"
+
+namespace cuttlefish {
+template <typename ProcessedFrameType>
+class ScreenConnectorInputMultiplexer {
+  using Queue = ScreenConnectorQueue<ProcessedFrameType>;
+  using Multiplexer = Multiplexer<ProcessedFrameType, Queue>;
+
+ public:
+  ScreenConnectorInputMultiplexer(HostModeCtrl& host_mode_ctrl)
+      : host_mode_ctrl_(host_mode_ctrl) {
+    sc_android_queue_id_ =
+        multiplexer_.RegisterQueue(multiplexer_.CreateQueue(/* q size */ 2));
+    sc_confui_queue_id_ =
+        multiplexer_.RegisterQueue(multiplexer_.CreateQueue(/* q size */ 2));
+  }
+
+  virtual ~ScreenConnectorInputMultiplexer() = default;
+
+  void PushToAndroidQueue(ProcessedFrameType&& t) {
+    multiplexer_.Push(sc_android_queue_id_, std::move(t));
+  }
+
+  void PushToConfUiQueue(ProcessedFrameType&& t) {
+    multiplexer_.Push(sc_confui_queue_id_, std::move(t));
+  }
+
+  // customize Pop()
+  ProcessedFrameType Pop() {
+    on_next_frame_cnt_++;
+
+    // is_discard_frame is thread-specific
+    bool is_discard_frame = false;
+
+    // callback to select the queue index, and update is_discard_frame
+    auto selector = [this, &is_discard_frame]() -> int {
+      if (multiplexer_.IsEmpty(sc_android_queue_id_)) {
+        ConfUiLog(VERBOSE)
+            << "Streamer gets Conf UI frame with host ctrl mode = "
+            << static_cast<std::uint32_t>(host_mode_ctrl_.GetMode())
+            << " and cnd = #" << on_next_frame_cnt_;
+        return sc_confui_queue_id_;
+      }
+      auto mode = host_mode_ctrl_.GetMode();
+      if (mode != HostModeCtrl::ModeType::kAndroidMode) {
+        // AndroidFrameFetchingLoop could have added 1 or 2 frames
+        // before it becomes Conf UI mode.
+        ConfUiLog(VERBOSE)
+            << "Streamer ignores Android frame with host ctrl mode ="
+            << static_cast<std::uint32_t>(mode) << "and cnd = #"
+            << on_next_frame_cnt_;
+        is_discard_frame = true;
+      }
+      ConfUiLog(VERBOSE) << "Streamer gets Android frame with host ctrl mode ="
+                         << static_cast<std::uint32_t>(mode) << "and cnd = #"
+                         << on_next_frame_cnt_;
+      return sc_android_queue_id_;
+    };
+
+    while (true) {
+      ConfUiLog(VERBOSE) << "Streamer waiting Semaphore with host ctrl mode ="
+                         << static_cast<std::uint32_t>(
+                                host_mode_ctrl_.GetMode())
+                         << " and cnd = #" << on_next_frame_cnt_;
+      auto processed_frame = multiplexer_.Pop(selector);
+      if (!is_discard_frame) {
+        return processed_frame;
+      }
+      is_discard_frame = false;
+    }
+  }
+
+ private:
+  HostModeCtrl& host_mode_ctrl_;
+  Multiplexer multiplexer_;
+  unsigned long long int on_next_frame_cnt_;
+  int sc_android_queue_id_;
+  int sc_confui_queue_id_;
+};
+}  // end of namespace cuttlefish
diff --git a/host/libs/screen_connector/screen_connector_queue.h b/host/libs/screen_connector/screen_connector_queue.h
index 2019168..66fd7f7 100644
--- a/host/libs/screen_connector/screen_connector_queue.h
+++ b/host/libs/screen_connector/screen_connector_queue.h
@@ -16,12 +16,11 @@
 
 #pragma once
 
+#include <condition_variable>
 #include <deque>
 #include <memory>
-#include <thread>
 #include <mutex>
-#include <condition_variable>
-#include <chrono>
+#include <thread>
 
 #include "common/libs/concurrency/semaphore.h"
 
@@ -31,19 +30,17 @@
 class ScreenConnectorQueue {
 
  public:
-  static const int kQSize = 2;
-
   static_assert( is_movable<T>::value,
                  "Items in ScreenConnectorQueue should be std::mov-able");
 
-  ScreenConnectorQueue(Semaphore& sc_sem)
-      : q_mutex_(std::make_unique<std::mutex>()), sc_semaphore_(sc_sem) {}
+  ScreenConnectorQueue(const int q_max_size = 2)
+      : q_mutex_(std::make_unique<std::mutex>()), q_max_size_{q_max_size} {}
   ScreenConnectorQueue(ScreenConnectorQueue&& cq) = delete;
   ScreenConnectorQueue(const ScreenConnectorQueue& cq) = delete;
   ScreenConnectorQueue& operator=(const ScreenConnectorQueue& cq) = delete;
   ScreenConnectorQueue& operator=(ScreenConnectorQueue&& cq) = delete;
 
-  bool Empty() const {
+  bool IsEmpty() const {
     const std::lock_guard<std::mutex> lock(*q_mutex_);
     return buffer_.empty();
   }
@@ -60,23 +57,23 @@
   }
 
   /*
-   * PushBack( std::move(src) );
+   * Push( std::move(src) );
    *
-   * Note: this queue is suppoed to be used only by ScreenConnector-
+   * Note: this queue is supposed to be used only by ScreenConnector-
    * related components such as ScreenConnectorSource
    *
-   * The traditional assumption was that when webRTC or VNC calls
+   * The traditional assumption was that when webRTC calls
    * OnFrameAfter, the call should be block until it could return
    * one frame.
    *
    * Thus, the producers of this queue must not produce frames
-   * much faster than the consumer, VNC or WebRTC consumes.
+   * much faster than the consumer, WebRTC consumes.
    * Therefore, when the small buffer is full -- which means
-   * VNC or WebRTC would not call OnFrameAfter --, the producer
+   * WebRTC would not call OnNextFrame --, the producer
    * should stop adding itmes to the queue.
    *
    */
-  void PushBack(T&& item) {
+  void Push(T&& item) {
     std::unique_lock<std::mutex> lock(*q_mutex_);
     if (Full()) {
       auto is_empty =
@@ -84,23 +81,11 @@
       q_empty_.wait(lock, is_empty);
     }
     buffer_.push_back(std::move(item));
-    /* Whether the total number of items in ALL queus is 0 or not
-     * is tracked via a semaphore shared by all queues
-     *
-     * This is NOT intended to block queue from pushing an item
-     * This IS intended to awake the screen_connector consumer thread
-     * when one or more items are available at least in one queue
-     */
-    sc_semaphore_.SemPost();
   }
-  void PushBack(T& item) = delete;
-  void PushBack(const T& item) = delete;
+  void Push(T& item) = delete;
+  void Push(const T& item) = delete;
 
-  /*
-   * PopFront must be preceded by sc_semaphore_.SemWaitItem()
-   *
-   */
-  T PopFront() {
+  T Pop() {
     const std::lock_guard<std::mutex> lock(*q_mutex_);
     auto item = std::move(buffer_.front());
     buffer_.pop_front();
@@ -114,12 +99,12 @@
   bool Full() const {
     // call this in a critical section
     // after acquiring q_mutex_
-    return kQSize == buffer_.size();
+    return q_max_size_ == buffer_.size();
   }
   std::deque<T> buffer_;
   std::unique_ptr<std::mutex> q_mutex_;
   std::condition_variable q_empty_;
-  Semaphore& sc_semaphore_;
+  const int q_max_size_;
 };
 
 } // namespace cuttlefish
diff --git a/host/libs/screen_connector/wayland_screen_connector.h b/host/libs/screen_connector/wayland_screen_connector.h
index 36ed322..ab3120b 100644
--- a/host/libs/screen_connector/wayland_screen_connector.h
+++ b/host/libs/screen_connector/wayland_screen_connector.h
@@ -16,23 +16,19 @@
 
 #pragma once
 
-#include "host/libs/screen_connector/screen_connector_common.h"
-
 #include <memory>
 
+#include "host/libs/screen_connector/screen_connector_common.h"
 #include "host/libs/wayland/wayland_server.h"
 
 namespace cuttlefish {
 
-class WaylandScreenConnector : public ScreenConnectorSource {
+class WaylandScreenConnector {
  public:
   WaylandScreenConnector(int frames_fd);
-
-  void SetFrameCallback(
-      GenerateProcessedFrameCallbackImpl frame_callback) override;
+  void SetFrameCallback(GenerateProcessedFrameCallbackImpl frame_callback);
 
  private:
   std::unique_ptr<wayland::WaylandServer> server_;
 };
-
 }
diff --git a/host/libs/vm_manager/Android.bp b/host/libs/vm_manager/Android.bp
index 20c9027..18bcb52 100644
--- a/host/libs/vm_manager/Android.bp
+++ b/host/libs/vm_manager/Android.bp
@@ -17,10 +17,34 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+soong_config_module_type {
+    name: "cf_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "cvdhost",
+    bool_variables: ["enforce_mac80211_hwsim"],
+    properties: ["cflags"],
+}
+
+// This is the customization layer driven by soong config variables.
+cf_cc_defaults {
+    name: "cvd_cc_defaults",
+    soong_config_variables: {
+        // PRODUCT_ENFORCE_MAC80211_HWSIM sets this
+        enforce_mac80211_hwsim: {
+            cflags: ["-DENFORCE_MAC80211_HWSIM=true"],
+            conditions_default: {
+                cflags: [],
+            }
+        },
+    }
+}
+
 cc_library_static {
     name: "libcuttlefish_vm_manager",
     srcs: [
+        "crosvm_builder.cpp",
         "crosvm_manager.cpp",
+        "gem5_manager.cpp",
         "host_configuration.cpp",
         "qemu_manager.cpp",
         "vm_manager.cpp",
@@ -32,10 +56,15 @@
         "libcuttlefish_fs",
         "libcuttlefish_utils",
         "libbase",
+        "libfruit",
         "libjsoncpp",
     ],
     static_libs: [
         "libcuttlefish_host_config",
     ],
-    defaults: ["cuttlefish_host", "cuttlefish_libicuuc"],
+    defaults: [
+        "cuttlefish_host",
+        "cuttlefish_libicuuc",
+        "cvd_cc_defaults",
+    ],
 }
diff --git a/host/libs/vm_manager/crosvm_builder.cpp b/host/libs/vm_manager/crosvm_builder.cpp
new file mode 100644
index 0000000..cb7fbc4
--- /dev/null
+++ b/host/libs/vm_manager/crosvm_builder.cpp
@@ -0,0 +1,106 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/libs/vm_manager/crosvm_builder.h"
+
+#include <android-base/logging.h>
+
+#include <string>
+
+#include "common/libs/utils/network.h"
+#include "common/libs/utils/subprocess.h"
+
+namespace cuttlefish {
+
+CrosvmBuilder::CrosvmBuilder() : command_("crosvm") {
+  command_.AddParameter("run");
+}
+
+void CrosvmBuilder::SetBinary(const std::string& binary) {
+  command_.SetExecutable(binary);
+}
+
+void CrosvmBuilder::AddControlSocket(const std::string& control_socket) {
+  // Store this value so it persists after std::move(this->Cmd())
+  auto crosvm = command_.Executable();
+  command_.SetStopper([crosvm, control_socket](Subprocess* proc) {
+    Command stop_cmd(crosvm);
+    stop_cmd.AddParameter("stop");
+    stop_cmd.AddParameter(control_socket);
+    if (stop_cmd.Start().Wait() == 0) {
+      return StopperResult::kStopSuccess;
+    }
+    LOG(WARNING) << "Failed to stop VMM nicely, attempting to KILL";
+    return KillSubprocess(proc) == StopperResult::kStopSuccess
+               ? StopperResult::kStopCrash
+               : StopperResult::kStopFailure;
+  });
+  command_.AddParameter("--socket=", control_socket);
+}
+
+void CrosvmBuilder::AddHvcSink() {
+  command_.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num_,
+                        ",type=sink");
+}
+void CrosvmBuilder::AddHvcConsoleReadOnly(const std::string& output) {
+  command_.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num_,
+                        ",type=file,path=", output, ",console=true");
+}
+void CrosvmBuilder::AddHvcReadOnly(const std::string& output) {
+  command_.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num_,
+                        ",type=file,path=", output);
+}
+void CrosvmBuilder::AddHvcReadWrite(const std::string& output,
+                                    const std::string& input) {
+  command_.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num_,
+                        ",type=file,path=", output, ",input=", input);
+}
+
+void CrosvmBuilder::AddSerialSink() {
+  command_.AddParameter("--serial=hardware=serial,num=", ++serial_num_,
+                        ",type=sink");
+}
+void CrosvmBuilder::AddSerialConsoleReadOnly(const std::string& output) {
+  command_.AddParameter("--serial=hardware=serial,num=", ++serial_num_,
+                        ",type=file,path=", output, ",earlycon=true");
+}
+void CrosvmBuilder::AddSerialConsoleReadWrite(const std::string& output,
+                                              const std::string& input) {
+  command_.AddParameter("--serial=hardware=serial,num=", ++serial_num_,
+                        ",type=file,path=", output, ",input=", input,
+                        ",earlycon=true");
+}
+void CrosvmBuilder::AddSerial(const std::string& output,
+                              const std::string& input) {
+  command_.AddParameter("--serial=hardware=serial,num=", ++serial_num_,
+                        ",type=file,path=", output, ",input=", input);
+}
+
+SharedFD CrosvmBuilder::AddTap(const std::string& tap_name) {
+  auto tap_fd = OpenTapInterface(tap_name);
+  if (tap_fd->IsOpen()) {
+    command_.AddParameter("--tap-fd=", tap_fd);
+  } else {
+    LOG(ERROR) << "Unable to connect to \"" << tap_name
+               << "\": " << tap_fd->StrError();
+  }
+  return tap_fd;
+}
+
+int CrosvmBuilder::HvcNum() { return hvc_num_; }
+
+Command& CrosvmBuilder::Cmd() { return command_; }
+
+}  // namespace cuttlefish
diff --git a/host/libs/vm_manager/crosvm_builder.h b/host/libs/vm_manager/crosvm_builder.h
new file mode 100644
index 0000000..90457b2
--- /dev/null
+++ b/host/libs/vm_manager/crosvm_builder.h
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <string>
+#include <utility>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/utils/subprocess.h"
+
+namespace cuttlefish {
+
+class CrosvmBuilder {
+ public:
+  CrosvmBuilder();
+
+  void SetBinary(const std::string&);
+  void AddControlSocket(const std::string&);
+
+  void AddHvcSink();
+  void AddHvcConsoleReadOnly(const std::string& output);
+  void AddHvcReadOnly(const std::string& output);
+  void AddHvcReadWrite(const std::string& output, const std::string& input);
+
+  void AddSerialSink();
+  void AddSerialConsoleReadOnly(const std::string& output);
+  void AddSerialConsoleReadWrite(const std::string& output,
+                                 const std::string& input);
+  // [[deprecated("do not add any more users")]]
+  void AddSerial(const std::string& output, const std::string& input);
+
+  SharedFD AddTap(const std::string& tap_name);
+
+  int HvcNum();
+
+  Command& Cmd();
+
+ private:
+  Command command_;
+  int hvc_num_;
+  int serial_num_;
+};
+
+}  // namespace cuttlefish
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index e424fd5..bf95250 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -16,23 +16,24 @@
 
 #include "host/libs/vm_manager/crosvm_manager.h"
 
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <vulkan/vulkan.h>
 
 #include <cassert>
 #include <string>
 #include <vector>
 
-#include <android-base/strings.h>
-#include <android-base/logging.h>
-#include <vulkan/vulkan.h>
-
 #include "common/libs/utils/environment.h"
+#include "common/libs/utils/files.h"
 #include "common/libs/utils/network.h"
 #include "common/libs/utils/subprocess.h"
-#include "common/libs/utils/files.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/known_paths.h"
+#include "host/libs/vm_manager/crosvm_builder.h"
 #include "host/libs/vm_manager/qemu_manager.h"
 
 namespace cuttlefish {
@@ -40,52 +41,10 @@
 
 namespace {
 
-std::string GetControlSocketPath(const CuttlefishConfig& config) {
-  return config.ForDefaultInstance()
-      .PerInstanceInternalPath("crosvm_control.sock");
-}
-
-SharedFD AddTapFdParameter(Command* crosvm_cmd,
-                                const std::string& tap_name) {
-  auto tap_fd = OpenTapInterface(tap_name);
-  if (tap_fd->IsOpen()) {
-    crosvm_cmd->AddParameter("--tap-fd=", tap_fd);
-  } else {
-    LOG(ERROR) << "Unable to connect to " << tap_name << ": "
-               << tap_fd->StrError();
-  }
-  return tap_fd;
-}
-
-bool ReleaseDhcpLeases(const std::string& lease_path, SharedFD tap_fd) {
-  auto lease_file_fd = SharedFD::Open(lease_path, O_RDONLY);
-  if (!lease_file_fd->IsOpen()) {
-    LOG(ERROR) << "Could not open leases file \"" << lease_path << '"';
-    return false;
-  }
-  bool success = true;
-  auto dhcp_leases = ParseDnsmasqLeases(lease_file_fd);
-  for (auto& lease : dhcp_leases) {
-    std::uint8_t dhcp_server_ip[] = {192, 168, 96, (std::uint8_t) (ForCurrentInstance(1) * 4 - 3)};
-    if (!ReleaseDhcp4(tap_fd, lease.mac_address, lease.ip_address, dhcp_server_ip)) {
-      LOG(ERROR) << "Failed to release " << lease;
-      success = false;
-    } else {
-      LOG(INFO) << "Successfully dropped " << lease;
-    }
-  }
-  return success;
-}
-
-bool Stop() {
-  auto config = CuttlefishConfig::Get();
-  Command command(config->crosvm_binary());
-  command.AddParameter("stop");
-  command.AddParameter(GetControlSocketPath(*config));
-
-  auto process = command.Start();
-
-  return process.Wait() == 0;
+std::string GetControlSocketPath(
+    const CuttlefishConfig::InstanceSpecific& instance,
+    const std::string& socket_name) {
+  return instance.PerInstanceInternalPath(socket_name.c_str());
 }
 
 }  // namespace
@@ -98,39 +57,39 @@
 #endif
 }
 
-std::vector<std::string> CrosvmManager::ConfigureGpuMode(
-    const std::string& gpu_mode) {
+std::vector<std::string> CrosvmManager::ConfigureGraphics(
+    const CuttlefishConfig& config) {
   // Override the default HAL search paths in all cases. We do this because
   // the HAL search path allows for fallbacks, and fallbacks in conjunction
   // with properities lead to non-deterministic behavior while loading the
   // HALs.
-  if (gpu_mode == kGpuModeGuestSwiftshader) {
+  if (config.gpu_mode() == kGpuModeGuestSwiftshader) {
     return {
         "androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_2),
         "androidboot.hardware.gralloc=minigbm",
-        "androidboot.hardware.hwcomposer=ranchu",
+        "androidboot.hardware.hwcomposer="+ config.hwcomposer(),
         "androidboot.hardware.egl=angle",
         "androidboot.hardware.vulkan=pastel",
-    };
+        "androidboot.opengles.version=196609"};  // OpenGL ES 3.1
   }
 
-  if (gpu_mode == kGpuModeDrmVirgl) {
+  if (config.gpu_mode() == kGpuModeDrmVirgl) {
     return {
       "androidboot.cpuvulkan.version=0",
       "androidboot.hardware.gralloc=minigbm",
-      "androidboot.hardware.hwcomposer=drm_minigbm",
+      "androidboot.hardware.hwcomposer=drm",
       "androidboot.hardware.egl=mesa",
     };
   }
-  if (gpu_mode == kGpuModeGfxStream) {
-    return {
-        "androidboot.cpuvulkan.version=0",
-        "androidboot.hardware.gralloc=minigbm",
-        "androidboot.hardware.hwcomposer=ranchu",
-        "androidboot.hardware.egl=emulation",
-        "androidboot.hardware.vulkan=ranchu",
-        "androidboot.hardware.gltransport=virtio-gpu-asg",
-    };
+  if (config.gpu_mode() == kGpuModeGfxStream) {
+    std::string gles_impl = config.enable_gpu_angle() ? "angle" : "emulation";
+    return {"androidboot.cpuvulkan.version=0",
+            "androidboot.hardware.gralloc=minigbm",
+            "androidboot.hardware.hwcomposer=" + config.hwcomposer(),
+            "androidboot.hardware.egl=" + gles_impl,
+            "androidboot.hardware.vulkan=ranchu",
+            "androidboot.hardware.gltransport=virtio-gpu-asg",
+            "androidboot.opengles.version=196608"};  // OpenGL ES 3.0
   }
   return {};
 }
@@ -139,7 +98,8 @@
   // TODO There is no way to control this assignment with crosvm (yet)
   if (HostArch() == Arch::X86_64) {
     // crosvm has an additional PCI device for an ISA bridge
-    return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 1, num_disks);
+    // virtio_gpu and virtio_wl precedes the first console or disk
+    return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 3, num_disks);
   } else {
     // On ARM64 crosvm, block devices are on their own bridge, so we don't
     // need to calculate it, and the path is always the same
@@ -147,110 +107,75 @@
   }
 }
 
+constexpr auto crosvm_socket = "crosvm_control.sock";
+
 std::vector<Command> CrosvmManager::StartCommands(
     const CuttlefishConfig& config) {
   auto instance = config.ForDefaultInstance();
-  Command crosvm_cmd(config.crosvm_binary(), [](Subprocess* proc) {
-    auto stopped = Stop();
-    if (stopped) {
-      return true;
-    }
-    LOG(WARNING) << "Failed to stop VMM nicely, attempting to KILL";
-    return KillSubprocess(proc);
-  });
-
-  int hvc_num = 0;
-  int serial_num = 0;
-  auto add_hvc_sink = [&crosvm_cmd, &hvc_num]() {
-    crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num,
-                            ",type=sink");
-  };
-  auto add_serial_sink = [&crosvm_cmd, &serial_num]() {
-    crosvm_cmd.AddParameter("--serial=hardware=serial,num=", ++serial_num,
-                            ",type=sink");
-  };
-  auto add_hvc_console = [&crosvm_cmd, &hvc_num](const std::string& output) {
-    crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num,
-                            ",type=file,path=", output, ",console=true");
-  };
-  auto add_serial_console_ro = [&crosvm_cmd,
-                                &serial_num](const std::string& output) {
-    crosvm_cmd.AddParameter("--serial=hardware=serial,num=", ++serial_num,
-                            ",type=file,path=", output, ",earlycon=true");
-  };
-  auto add_serial_console = [&crosvm_cmd, &serial_num](
-                                const std::string& output,
-                                const std::string& input) {
-    crosvm_cmd.AddParameter("--serial=hardware=serial,num=", ++serial_num,
-                            ",type=file,path=", output, ",input=", input,
-                            ",earlycon=true");
-  };
-  auto add_hvc_ro = [&crosvm_cmd, &hvc_num](const std::string& output) {
-    crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num,
-                            ",type=file,path=", output);
-  };
-  auto add_hvc = [&crosvm_cmd, &hvc_num](const std::string& output,
-                                         const std::string& input) {
-    crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num,
-                            ",type=file,path=", output, ",input=", input);
-  };
-  // Deprecated; do not add any more users
-  auto add_serial = [&crosvm_cmd, &serial_num](const std::string& output,
-                                               const std::string& input) {
-    crosvm_cmd.AddParameter("--serial=hardware=serial,num=", ++serial_num,
-                            ",type=file,path=", output, ",input=", input);
-  };
-
-  crosvm_cmd.AddParameter("run");
+  CrosvmBuilder crosvm_cmd;
+  crosvm_cmd.SetBinary(config.crosvm_binary());
+  crosvm_cmd.AddControlSocket(GetControlSocketPath(instance, crosvm_socket));
 
   if (!config.smt()) {
-    crosvm_cmd.AddParameter("--no-smt");
+    crosvm_cmd.Cmd().AddParameter("--no-smt");
   }
 
   if (config.vhost_net()) {
-    crosvm_cmd.AddParameter("--vhost-net");
+    crosvm_cmd.Cmd().AddParameter("--vhost-net");
   }
 
+#ifdef ENFORCE_MAC80211_HWSIM
+  if (!config.vhost_user_mac80211_hwsim().empty()) {
+    crosvm_cmd.Cmd().AddParameter("--vhost-user-mac80211-hwsim=",
+                                  config.vhost_user_mac80211_hwsim());
+  }
+#endif
+
   if (config.protected_vm()) {
-    crosvm_cmd.AddParameter("--protected-vm");
+    crosvm_cmd.Cmd().AddParameter("--protected-vm");
   }
 
   if (config.gdb_port() > 0) {
     CHECK(config.cpus() == 1) << "CPUs must be 1 for crosvm gdb mode";
-    crosvm_cmd.AddParameter("--gdb=", config.gdb_port());
+    crosvm_cmd.Cmd().AddParameter("--gdb=", config.gdb_port());
   }
 
+  auto gpu_capture_enabled = !config.gpu_capture_binary().empty();
   auto gpu_mode = config.gpu_mode();
+  auto udmabuf_string = config.enable_gpu_udmabuf() ? "true" : "false";
+  auto angle_string = config.enable_gpu_angle() ? ",angle=true" : "";
   if (gpu_mode == kGpuModeGuestSwiftshader) {
-    crosvm_cmd.AddParameter("--gpu=2D");
+    crosvm_cmd.Cmd().AddParameter("--gpu=2D,udmabuf=", udmabuf_string);
   } else if (gpu_mode == kGpuModeDrmVirgl || gpu_mode == kGpuModeGfxStream) {
-    crosvm_cmd.AddParameter(gpu_mode == kGpuModeGfxStream ?
-                                "--gpu=gfxstream," : "--gpu=",
-                            "egl=true,surfaceless=true,glx=false,gles=true");
+    crosvm_cmd.Cmd().AddParameter(
+        gpu_mode == kGpuModeGfxStream ? "--gpu=gfxstream," : "--gpu=",
+        "egl=true,surfaceless=true,glx=false,gles=true,udmabuf=", udmabuf_string,
+        angle_string);
   }
 
   for (const auto& display_config : config.display_configs()) {
-    crosvm_cmd.AddParameter("--gpu-display=", "width=", display_config.width,
-                            ",", "height=", display_config.height);
+    crosvm_cmd.Cmd().AddParameter(
+        "--gpu-display=", "width=", display_config.width, ",",
+        "height=", display_config.height);
   }
 
-  crosvm_cmd.AddParameter("--wayland-sock=", instance.frames_socket_path());
+  crosvm_cmd.Cmd().AddParameter("--wayland-sock=",
+                                instance.frames_socket_path());
 
-  // crosvm_cmd.AddParameter("--null-audio");
-  crosvm_cmd.AddParameter("--mem=", config.memory_mb());
-  crosvm_cmd.AddParameter("--cpus=", config.cpus());
+  // crosvm_cmd.Cmd().AddParameter("--null-audio");
+  crosvm_cmd.Cmd().AddParameter("--mem=", config.memory_mb());
+  crosvm_cmd.Cmd().AddParameter("--cpus=", config.cpus());
 
   auto disk_num = instance.virtual_disk_paths().size();
   CHECK_GE(VmManager::kMaxDisks, disk_num)
       << "Provided too many disks (" << disk_num << "), maximum "
       << VmManager::kMaxDisks << "supported";
   for (const auto& disk : instance.virtual_disk_paths()) {
-    crosvm_cmd.AddParameter(config.protected_vm() ? "--disk=" :
-                                                    "--rwdisk=", disk);
+    crosvm_cmd.Cmd().AddParameter(
+        config.protected_vm() ? "--disk=" : "--rwdisk=", disk);
   }
-  crosvm_cmd.AddParameter("--socket=", GetControlSocketPath(config));
 
-  if (config.enable_vnc_server() || config.enable_webrtc()) {
+  if (config.enable_webrtc()) {
     auto touch_type_parameter =
         config.enable_webrtc() ? "--multi-touch=" : "--single-touch=";
 
@@ -260,28 +185,45 @@
     for (int i = 0; i < display_configs.size(); ++i) {
       auto display_config = display_configs[i];
 
-      crosvm_cmd.AddParameter(touch_type_parameter,
-                              instance.touch_socket_path(i), ":",
-                              display_config.width, ":", display_config.height);
+      crosvm_cmd.Cmd().AddParameter(
+          touch_type_parameter, instance.touch_socket_path(i), ":",
+          display_config.width, ":", display_config.height);
     }
-    crosvm_cmd.AddParameter("--keyboard=", instance.keyboard_socket_path());
+    crosvm_cmd.Cmd().AddParameter("--keyboard=",
+                                  instance.keyboard_socket_path());
   }
   if (config.enable_webrtc()) {
-    crosvm_cmd.AddParameter("--switches=", instance.switches_socket_path());
+    crosvm_cmd.Cmd().AddParameter("--switches=",
+                                  instance.switches_socket_path());
   }
 
-  AddTapFdParameter(&crosvm_cmd, instance.mobile_tap_name());
-  AddTapFdParameter(&crosvm_cmd, instance.ethernet_tap_name());
-  auto wifi_tap = AddTapFdParameter(&crosvm_cmd, instance.wifi_tap_name());
+  SharedFD wifi_tap;
+  // GPU capture can only support named files and not file descriptors due to
+  // having to pass arguments to crosvm via a wrapper script.
+  if (!gpu_capture_enabled) {
+    crosvm_cmd.AddTap(instance.mobile_tap_name());
+    crosvm_cmd.AddTap(instance.ethernet_tap_name());
+
+    // TODO(b/199103204): remove this as well when
+    // PRODUCT_ENFORCE_MAC80211_HWSIM is removed
+#ifndef ENFORCE_MAC80211_HWSIM
+    wifi_tap = crosvm_cmd.AddTap(instance.wifi_tap_name());
+#endif
+  }
 
   if (FileExists(instance.access_kregistry_path())) {
-    crosvm_cmd.AddParameter("--rw-pmem-device=",
-                            instance.access_kregistry_path());
+    crosvm_cmd.Cmd().AddParameter("--rw-pmem-device=",
+                                  instance.access_kregistry_path());
+  }
+
+  if (FileExists(instance.hwcomposer_pmem_path())) {
+    crosvm_cmd.Cmd().AddParameter("--rw-pmem-device=",
+                                  instance.hwcomposer_pmem_path());
   }
 
   if (FileExists(instance.pstore_path())) {
-    crosvm_cmd.AddParameter("--pstore=path=", instance.pstore_path(),
-                            ",size=", FileSize(instance.pstore_path()));
+    crosvm_cmd.Cmd().AddParameter("--pstore=path=", instance.pstore_path(),
+                                  ",size=", FileSize(instance.pstore_path()));
   }
 
   if (config.enable_sandbox()) {
@@ -294,19 +236,20 @@
                  << " does not exist " << std::endl;
       return {};
     }
-    crosvm_cmd.AddParameter("--seccomp-policy-dir=", config.seccomp_policy_dir());
+    crosvm_cmd.Cmd().AddParameter("--seccomp-policy-dir=",
+                                  config.seccomp_policy_dir());
   } else {
-    crosvm_cmd.AddParameter("--disable-sandbox");
+    crosvm_cmd.Cmd().AddParameter("--disable-sandbox");
   }
 
   if (instance.vsock_guest_cid() >= 2) {
-    crosvm_cmd.AddParameter("--cid=", instance.vsock_guest_cid());
+    crosvm_cmd.Cmd().AddParameter("--cid=", instance.vsock_guest_cid());
   }
 
   // Use a virtio-console instance for the main kernel console. All
   // messages will switch from earlycon to virtio-console after the driver
   // is loaded, and crosvm will append to the kernel log automatically
-  add_hvc_console(instance.kernel_log_pipe_name());
+  crosvm_cmd.AddHvcConsoleReadOnly(instance.kernel_log_pipe_name());
 
   if (config.console()) {
     // stdin is the only currently supported way to write data to a serial port in
@@ -314,18 +257,18 @@
     // the serial port output is received by the console forwarder as crosvm may
     // print other messages to stdout.
     if (config.kgdb() || config.use_bootloader()) {
-      add_serial_console(instance.console_out_pipe_name(),
-                         instance.console_in_pipe_name());
+      crosvm_cmd.AddSerialConsoleReadWrite(instance.console_out_pipe_name(),
+                                           instance.console_in_pipe_name());
       // In kgdb mode, we have the interactive console on ttyS0 (both Android's
       // console and kdb), so we can disable the virtio-console port usually
       // allocated to Android's serial console, and redirect it to a sink. This
       // ensures that that the PCI device assignments (and thus sepolicy) don't
       // have to change
-      add_hvc_sink();
+      crosvm_cmd.AddHvcSink();
     } else {
-      add_serial_sink();
-      add_hvc(instance.console_out_pipe_name(),
-              instance.console_in_pipe_name());
+      crosvm_cmd.AddSerialSink();
+      crosvm_cmd.AddHvcReadWrite(instance.console_out_pipe_name(),
+                                 instance.console_in_pipe_name());
     }
   } else {
     // Use an 8250 UART (ISA or platform device) for earlycon, as the
@@ -333,86 +276,165 @@
     // In kgdb mode, earlycon is an interactive console, and so early
     // dmesg will go there instead of the kernel.log
     if (config.kgdb() || config.use_bootloader()) {
-      add_serial_console_ro(instance.kernel_log_pipe_name());
+      crosvm_cmd.AddSerialConsoleReadOnly(instance.kernel_log_pipe_name());
     }
 
     // as above, create a fake virtio-console 'sink' port when the serial
     // console is disabled, so the PCI device ID assignments don't move
     // around
-    add_hvc_sink();
+    crosvm_cmd.AddHvcSink();
   }
 
-  if (config.enable_gnss_grpc_proxy()) {
-    add_serial(instance.gnss_out_pipe_name(), instance.gnss_in_pipe_name());
-  }
-
-  SharedFD log_out_rd, log_out_wr;
-  if (!SharedFD::Pipe(&log_out_rd, &log_out_wr)) {
-    LOG(ERROR) << "Failed to create log pipe for crosvm's stdout/stderr: "
-               << log_out_rd->StrError();
+  auto crosvm_logs_path = instance.PerInstanceInternalPath("crosvm.fifo");
+  auto crosvm_logs = SharedFD::Fifo(crosvm_logs_path, 0666);
+  if (!crosvm_logs->IsOpen()) {
+    LOG(FATAL) << "Failed to create log fifo for crosvm's stdout/stderr: "
+               << crosvm_logs->StrError();
     return {};
   }
-  crosvm_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, log_out_wr);
-  crosvm_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, log_out_wr);
 
-  Command log_tee_cmd(HostBinaryPath("log_tee"));
-  log_tee_cmd.AddParameter("--process_name=crosvm");
-  log_tee_cmd.AddParameter("--log_fd_in=", log_out_rd);
+  Command crosvm_log_tee_cmd(HostBinaryPath("log_tee"));
+  crosvm_log_tee_cmd.AddParameter("--process_name=crosvm");
+  crosvm_log_tee_cmd.AddParameter("--log_fd_in=", crosvm_logs);
 
   // Serial port for logcat, redirected to a pipe
-  add_hvc_ro(instance.logcat_pipe_name());
+  crosvm_cmd.AddHvcReadOnly(instance.logcat_pipe_name());
 
-  add_hvc(instance.PerInstanceInternalPath("keymaster_fifo_vm.out"),
-          instance.PerInstanceInternalPath("keymaster_fifo_vm.in"));
-  add_hvc(instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
-          instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"));
+  crosvm_cmd.AddHvcReadWrite(
+      instance.PerInstanceInternalPath("keymaster_fifo_vm.out"),
+      instance.PerInstanceInternalPath("keymaster_fifo_vm.in"));
+  crosvm_cmd.AddHvcReadWrite(
+      instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
+      instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"));
 
   if (config.enable_host_bluetooth()) {
-    add_hvc(instance.PerInstanceInternalPath("bt_fifo_vm.out"),
-            instance.PerInstanceInternalPath("bt_fifo_vm.in"));
+    crosvm_cmd.AddHvcReadWrite(
+        instance.PerInstanceInternalPath("bt_fifo_vm.out"),
+        instance.PerInstanceInternalPath("bt_fifo_vm.in"));
   } else {
-    add_hvc_sink();
+    crosvm_cmd.AddHvcSink();
   }
+  if (config.enable_gnss_grpc_proxy()) {
+    crosvm_cmd.AddHvcReadWrite(
+        instance.PerInstanceInternalPath("gnsshvc_fifo_vm.out"),
+        instance.PerInstanceInternalPath("gnsshvc_fifo_vm.in"));
+    crosvm_cmd.AddHvcReadWrite(
+        instance.PerInstanceInternalPath("locationhvc_fifo_vm.out"),
+        instance.PerInstanceInternalPath("locationhvc_fifo_vm.in"));
+  } else {
+    for (auto i = 0; i < 2; i++) {
+      crosvm_cmd.AddHvcSink();
+    }
+  }
+
   for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
-    add_hvc_sink();
+    crosvm_cmd.AddHvcSink();
   }
-  CHECK(hvc_num + disk_num == VmManager::kMaxDisks + VmManager::kDefaultNumHvcs)
-      << "HVC count (" << hvc_num << ") + disk count (" << disk_num << ") "
-      << "is not the expected total of "
+  CHECK(crosvm_cmd.HvcNum() + disk_num ==
+        VmManager::kMaxDisks + VmManager::kDefaultNumHvcs)
+      << "HVC count (" << crosvm_cmd.HvcNum() << ") + disk count (" << disk_num
+      << ") is not the expected total of "
       << VmManager::kMaxDisks + VmManager::kDefaultNumHvcs << " devices";
 
   if (config.enable_audio()) {
-    crosvm_cmd.AddParameter("--sound=",
-                            config.ForDefaultInstance().audio_server_path());
+    crosvm_cmd.Cmd().AddParameter(
+        "--sound=", config.ForDefaultInstance().audio_server_path());
   }
 
   // TODO(b/162071003): virtiofs crashes without sandboxing, this should be fixed
-  if (config.enable_sandbox()) {
+  if (0 && config.enable_sandbox()) {
     // Set up directory shared with virtiofs
-    crosvm_cmd.AddParameter("--shared-dir=", instance.PerInstancePath(kSharedDirName),
-                            ":shared:type=fs");
+    crosvm_cmd.Cmd().AddParameter(
+        "--shared-dir=", instance.PerInstancePath(kSharedDirName),
+        ":shared:type=fs");
   }
 
   // This needs to be the last parameter
-  crosvm_cmd.AddParameter("--bios=", config.bootloader());
+  crosvm_cmd.Cmd().AddParameter("--bios=", config.bootloader());
 
+  // TODO(b/199103204): remove this as well when PRODUCT_ENFORCE_MAC80211_HWSIM
+  // is removed
   // Only run the leases workaround if we are not using the new network
   // bridge architecture - in that case, we have a wider DHCP address
   // space and stale leases should be much less of an issue
-  if (!FileExists("/var/run/cuttlefish-dnsmasq-cvd-wbr.leases")) {
+  if (!FileExists("/var/run/cuttlefish-dnsmasq-cvd-wbr.leases") &&
+      wifi_tap->IsOpen()) {
     // TODO(schuffelen): QEMU also needs this and this is not the best place for
     // this code. Find a better place to put it.
     auto lease_file =
         ForCurrentInstance("/var/run/cuttlefish-dnsmasq-cvd-wbr-") + ".leases";
-    if (!ReleaseDhcpLeases(lease_file, wifi_tap)) {
+
+    std::uint8_t dhcp_server_ip[] = {
+        192, 168, 96, (std::uint8_t)(ForCurrentInstance(1) * 4 - 3)};
+    if (!ReleaseDhcpLeases(lease_file, wifi_tap, dhcp_server_ip)) {
       LOG(ERROR) << "Failed to release wifi DHCP leases. Connecting to the wifi "
                  << "network may not work.";
     }
   }
 
   std::vector<Command> ret;
-  ret.push_back(std::move(crosvm_cmd));
-  ret.push_back(std::move(log_tee_cmd));
+
+  if (gpu_capture_enabled) {
+    const std::string gpu_capture_basename =
+        cpp_basename(config.gpu_capture_binary());
+
+    auto gpu_capture_logs_path =
+        instance.PerInstanceInternalPath("gpu_capture.fifo");
+    auto gpu_capture_logs = SharedFD::Fifo(gpu_capture_logs_path, 0666);
+    if (!gpu_capture_logs->IsOpen()) {
+      LOG(FATAL)
+          << "Failed to create log fifo for gpu capture's stdout/stderr: "
+          << gpu_capture_logs->StrError();
+      return {};
+    }
+
+    Command gpu_capture_log_tee_cmd(HostBinaryPath("log_tee"));
+    gpu_capture_log_tee_cmd.AddParameter("--process_name=",
+                                         gpu_capture_basename);
+    gpu_capture_log_tee_cmd.AddParameter("--log_fd_in=", gpu_capture_logs);
+
+    Command gpu_capture_command(config.gpu_capture_binary());
+    if (gpu_capture_basename == "ngfx") {
+      // Crosvm depends on command line arguments being passed as multiple
+      // arguments but ngfx only allows a single `--args`. To work around this,
+      // create a wrapper script that launches crosvm with all of the arguments
+      // and pass this wrapper script to ngfx.
+      const std::string crosvm_wrapper_path =
+          instance.PerInstanceInternalPath("crosvm_wrapper.sh");
+      const std::string crosvm_wrapper_content =
+          crosvm_cmd.Cmd().AsBashScript(crosvm_logs_path);
+
+      CHECK(android::base::WriteStringToFile(crosvm_wrapper_content,
+                                             crosvm_wrapper_path));
+      CHECK(MakeFileExecutable(crosvm_wrapper_path));
+
+      gpu_capture_command.AddParameter("--exe=", crosvm_wrapper_path);
+      gpu_capture_command.AddParameter("--launch-detached");
+      gpu_capture_command.AddParameter("--verbose");
+      gpu_capture_command.AddParameter("--activity=Frame Debugger");
+    } else {
+      // TODO(natsu): renderdoc
+      LOG(FATAL) << "Unhandled GPU capture binary: "
+                 << config.gpu_capture_binary();
+    }
+
+    gpu_capture_command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
+                                      gpu_capture_logs);
+    gpu_capture_command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr,
+                                      gpu_capture_logs);
+
+    ret.push_back(std::move(gpu_capture_log_tee_cmd));
+    ret.push_back(std::move(gpu_capture_command));
+  } else {
+    crosvm_cmd.Cmd().RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
+                                   crosvm_logs);
+    crosvm_cmd.Cmd().RedirectStdIO(Subprocess::StdIOChannel::kStdErr,
+                                   crosvm_logs);
+
+    ret.push_back(std::move(crosvm_cmd.Cmd()));
+  }
+
+  ret.push_back(std::move(crosvm_log_tee_cmd));
   return ret;
 }
 
diff --git a/host/libs/vm_manager/crosvm_manager.h b/host/libs/vm_manager/crosvm_manager.h
index 9d41d68..bfd5b79 100644
--- a/host/libs/vm_manager/crosvm_manager.h
+++ b/host/libs/vm_manager/crosvm_manager.h
@@ -31,11 +31,11 @@
 class CrosvmManager : public VmManager {
  public:
   static std::string name() { return "crosvm"; }
-  CrosvmManager(Arch arch) : VmManager(arch) {}
   virtual ~CrosvmManager() = default;
 
   bool IsSupported() override;
-  std::vector<std::string> ConfigureGpuMode(const std::string&) override;
+  std::vector<std::string> ConfigureGraphics(
+      const CuttlefishConfig& config) override;
   std::string ConfigureBootDevices(int num_disks) override;
 
   std::vector<cuttlefish::Command> StartCommands(
diff --git a/host/libs/vm_manager/gem5_manager.cpp b/host/libs/vm_manager/gem5_manager.cpp
new file mode 100644
index 0000000..ef9d2cd
--- /dev/null
+++ b/host/libs/vm_manager/gem5_manager.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/vm_manager/gem5_manager.h"
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <cstdlib>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/strings.h>
+#include <android-base/logging.h>
+#include <vulkan/vulkan.h>
+
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/subprocess.h"
+#include "common/libs/utils/users.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
+
+namespace cuttlefish {
+namespace vm_manager {
+namespace {
+
+void LogAndSetEnv(const char* key, const std::string& value) {
+  setenv(key, value.c_str(), 1);
+  LOG(INFO) << key << "=" << value;
+}
+
+void GenerateGem5File(const CuttlefishConfig& config) {
+  // Gem5 specific config, currently users have to change these config locally (without throug launch_cvd input flag) to meet their design
+  // TODO: Add these config into launch_cvd input flag or parse from one json file
+  std::string cpu_class = "AtomicSimpleCPU";
+  std::string l1_icache_class = "None";
+  std::string l1_dcache_class = "None";
+  std::string walk_cache_class = "None";
+  std::string l2_Cache_class = "None";
+  std::string cpu_freq = "4GHz";
+  int num_cores = 1;
+  std::string mem_type = "DDR3_1600_8x8";
+  int mem_channels = 1;
+  std::string mem_ranks = "None";
+
+  // start generating starter_fs.py
+  std::string fs_path = config.gem5_binary_dir() + "/configs/example/arm/starter_fs.py";
+  std::ofstream starter_fs_ofstream(fs_path.c_str());
+  starter_fs_ofstream << fs_header << "\n";
+
+  // global vars in python
+  starter_fs_ofstream << "default_disk = 'linaro-minimal-aarch64.img'\n";
+
+  // main function
+  starter_fs_ofstream << "def main():\n";
+
+  // args
+  auto instance = config.ForDefaultInstance();
+  starter_fs_ofstream << "  parser = argparse.ArgumentParser(epilog=__doc__)\n";
+  starter_fs_ofstream << "  parser.add_argument(\"--disk-image\", action=\"append\", type=str, default=[])\n";
+  starter_fs_ofstream << "  parser.add_argument(\"--mem-type\", default=\"" << mem_type << "\", choices=ObjectList.mem_list.get_names())\n";
+  starter_fs_ofstream << "  parser.add_argument(\"--mem-channels\", type=int, default=" << mem_channels << ")\n";
+  starter_fs_ofstream << "  parser.add_argument(\"--mem-ranks\", type=int, default=" << mem_ranks << ")\n";
+  starter_fs_ofstream << "  parser.add_argument(\"--mem-size\", action=\"store\", type=str, default=\"" << config.memory_mb() << "MB\")\n";
+  starter_fs_ofstream << "  args = parser.parse_args()\n";
+
+  // instantiate system
+  starter_fs_ofstream << "  root = Root(full_system=True)\n";
+  starter_fs_ofstream << "  mem_mode = " << cpu_class << ".memory_mode()\n";
+  starter_fs_ofstream << "  has_caches = True if mem_mode == \"timing\" else False\n";
+  starter_fs_ofstream << "  root.system = devices.SimpleSystem(has_caches, args.mem_size, mem_mode=mem_mode, workload=ArmFsLinux(object_file=SysPaths.binary(\"" << config.assembly_dir() << "/kernel\")))\n";
+
+  // mem config and pci instantiate
+  starter_fs_ofstream << fs_mem_pci;
+
+  // system settings
+  starter_fs_ofstream << "  root.system.cpu_cluster = [devices.CpuCluster(root.system, " << num_cores << ", \"" << cpu_freq << "\", \"1.0V\", " << cpu_class << ", " << l1_icache_class << ", " << l1_dcache_class << ", " << walk_cache_class << ", " << l2_Cache_class << ")]\n";
+  starter_fs_ofstream << "  root.system.addCaches(has_caches, last_cache_level=2)\n";
+  starter_fs_ofstream << "  root.system.realview.setupBootLoader(root.system, SysPaths.binary)\n";
+  starter_fs_ofstream << "  root.system.workload.dtb_filename = os.path.join(m5.options.outdir, 'system.dtb')\n";
+  starter_fs_ofstream << "  root.system.generateDtb(root.system.workload.dtb_filename)\n";
+  starter_fs_ofstream << "  root.system.workload.initrd_filename = \"" << instance.PerInstancePath("initrd.img") << "\"\n";
+
+  //kernel cmd
+  starter_fs_ofstream << fs_kernel_cmd << "\n";
+
+  // execute main
+  starter_fs_ofstream << fs_exe_main << "\n";
+}
+
+}  // namespace
+
+Gem5Manager::Gem5Manager(Arch arch) : arch_(arch) {}
+
+bool Gem5Manager::IsSupported() {
+  return HostSupportsQemuCli();
+}
+
+std::vector<std::string> Gem5Manager::ConfigureGraphics(
+    const CuttlefishConfig& config) {
+  // TODO: Add support for the gem5 gpu models
+
+  // Override the default HAL search paths in all cases. We do this because
+  // the HAL search path allows for fallbacks, and fallbacks in conjunction
+  // with properities lead to non-deterministic behavior while loading the
+  // HALs.
+  return {
+      "androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_1),
+      "androidboot.hardware.gralloc=minigbm",
+      "androidboot.hardware.hwcomposer=" + config.hwcomposer(),
+      "androidboot.hardware.hwcomposer.mode=noop",
+      "androidboot.hardware.egl=angle",
+      "androidboot.hardware.vulkan=pastel",
+  };
+}
+
+std::string Gem5Manager::ConfigureBootDevices(int /*num_disks*/) {
+  switch (arch_) {
+    case Arch::Arm:
+    case Arch::Arm64:
+      return "androidboot.boot_devices=30000000.pci";
+    // TODO: Add x86 support
+    default:
+      return "";
+  }
+}
+
+std::vector<Command> Gem5Manager::StartCommands(
+    const CuttlefishConfig& config) {
+  auto instance = config.ForDefaultInstance();
+
+  auto stop = [](Subprocess* proc) {
+    return KillSubprocess(proc) == StopperResult::kStopSuccess
+               ? StopperResult::kStopCrash
+               : StopperResult::kStopFailure;
+  };
+  std::string gem5_binary = config.gem5_binary_dir();
+  switch (arch_) {
+    case Arch::Arm:
+    case Arch::Arm64:
+      gem5_binary += "/build/ARM/gem5.opt";
+      break;
+    case Arch::X86:
+    case Arch::X86_64:
+      gem5_binary += "/build/X86/gem5.opt";
+      break;
+  }
+  // generate Gem5 starter_fs.py before we execute it
+  GenerateGem5File(config);
+
+  Command gem5_cmd(gem5_binary, stop);
+  gem5_cmd.AddParameter(config.gem5_binary_dir(), "/configs/example/arm/starter_fs.py");
+  gem5_cmd.AddParameter("--mem-size=", config.memory_mb() * 1024ULL * 1024ULL);
+  for (const auto& disk : instance.virtual_disk_paths()) {
+    gem5_cmd.AddParameter("--disk-image=", disk);
+  }
+
+  LogAndSetEnv("M5_PATH", config.assembly_dir());
+
+  std::vector<Command> ret;
+  ret.push_back(std::move(gem5_cmd));
+  return ret;
+}
+
+} // namespace vm_manager
+} // namespace cuttlefish
diff --git a/host/libs/vm_manager/gem5_manager.h b/host/libs/vm_manager/gem5_manager.h
new file mode 100644
index 0000000..1c19683
--- /dev/null
+++ b/host/libs/vm_manager/gem5_manager.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "host/libs/vm_manager/vm_manager.h"
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+namespace vm_manager {
+
+// Starts a guest VM using the gem5 command directly. It requires the host
+// package to support the gem5 capability.
+class Gem5Manager : public VmManager {
+ public:
+  static std::string name() { return "gem5"; }
+
+  Gem5Manager(Arch);
+  virtual ~Gem5Manager() = default;
+
+  bool IsSupported() override;
+  std::vector<std::string> ConfigureGraphics(
+      const CuttlefishConfig& config) override;
+  std::string ConfigureBootDevices(int num_disks) override;
+
+  std::vector<cuttlefish::Command> StartCommands(
+      const CuttlefishConfig& config) override;
+
+ private:
+  Arch arch_;
+};
+
+const std::string fs_header = R"CPP_STR_END(import argparse
+import devices
+import os
+import m5
+from m5.util import addToPath
+from m5.objects import *
+from m5.options import *
+from common import SysPaths
+from common import ObjectList
+from common import MemConfig
+from common.cores.arm import HPI
+m5.util.addToPath('../..')
+)CPP_STR_END";
+
+const std::string fs_mem_pci = R"CPP_STR_END(
+  MemConfig.config_mem(args, root.system)
+
+  pci_devices = []
+  pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=0))))
+  pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=1, outfile="none"))))
+  pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=2))))
+  pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=3, outfile="none"))))
+  pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=4, outfile="none"))))
+  pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=5, outfile="none"))))
+  pci_devices.append(PciVirtIO(vio=VirtIOConsole(device=Terminal(number=6, outfile="none"))))
+
+  for each_item in args.disk_image:
+    disk_image = CowDiskImage()
+    disk_image.child.image_file = SysPaths.disk(each_item)
+    pci_devices.append(PciVirtIO(vio=VirtIOBlock(image=disk_image)))
+
+  root.system.pci_devices = pci_devices
+  for pci_device in root.system.pci_devices:
+    root.system.attach_pci(pci_device)
+
+  root.system.connect()
+)CPP_STR_END";
+
+const std::string fs_kernel_cmd = R"CPP_STR_END(
+  kernel_cmd = [
+    "lpj=19988480",
+    "norandmaps",
+    "mem=%s" % args.mem_size,
+    "console=hvc0",
+    "panic=-1",
+    "earlycon=pl011,mmio32,0x1c090000",
+    "audit=1",
+    "printk.devkmsg=on",
+    "firmware_class.path=/vendor/etc/",
+    "kfence.sample_interval=500",
+    "loop.max_part=7",
+    "bootconfig",
+    "androidboot.force_normal_boot=1",
+  ]
+  root.system.workload.command_line = " ".join(kernel_cmd)
+  m5.instantiate()
+  sys.exit(m5.simulate().getCode())
+)CPP_STR_END";
+
+const std::string fs_exe_main = R"CPP_STR_END(
+if __name__ == "__m5_main__":
+  main()
+)CPP_STR_END";
+
+} // namespace vm_manager
+} // namespace cuttlefish
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index c8c1084..adedf04 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -85,15 +85,47 @@
   return true;
 }
 
+std::pair<int,int> GetQemuVersion(const std::string& qemu_binary)
+{
+  Command qemu_version_cmd(qemu_binary);
+  qemu_version_cmd.AddParameter("-version");
+
+  std::string qemu_version_input, qemu_version_output, qemu_version_error;
+  cuttlefish::SubprocessOptions options;
+  options.Verbose(false);
+  int qemu_version_ret =
+      cuttlefish::RunWithManagedStdio(std::move(qemu_version_cmd),
+                                      &qemu_version_input,
+                                      &qemu_version_output,
+                                      &qemu_version_error, options);
+  if (qemu_version_ret != 0) {
+    LOG(FATAL) << qemu_binary << " -version returned unexpected response "
+               << qemu_version_output << ". Stderr was " << qemu_version_error;
+    return { 0, 0 };
+  }
+
+  // Snip around the extra text we don't care about
+  qemu_version_output.erase(0, std::string("QEMU emulator version ").length());
+  auto space_pos = qemu_version_output.find(" ", 0);
+  if (space_pos != std::string::npos) {
+    qemu_version_output.resize(space_pos);
+  }
+
+  auto qemu_version_bits = android::base::Split(qemu_version_output, ".");
+  return { std::stoi(qemu_version_bits[0]), std::stoi(qemu_version_bits[1]) };
+}
+
 }  // namespace
 
+QemuManager::QemuManager(Arch arch) : arch_(arch) {}
+
 bool QemuManager::IsSupported() {
   return HostSupportsQemuCli();
 }
 
-std::vector<std::string> QemuManager::ConfigureGpuMode(
-    const std::string& gpu_mode) {
-  if (gpu_mode == kGpuModeGuestSwiftshader) {
+std::vector<std::string> QemuManager::ConfigureGraphics(
+    const CuttlefishConfig& config) {
+  if (config.gpu_mode() == kGpuModeGuestSwiftshader) {
     // Override the default HAL search paths in all cases. We do this because
     // the HAL search path allows for fallbacks, and fallbacks in conjunction
     // with properities lead to non-deterministic behavior while loading the
@@ -101,17 +133,17 @@
     return {
         "androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_1),
         "androidboot.hardware.gralloc=minigbm",
-        "androidboot.hardware.hwcomposer=ranchu",
-        "androidboot.hardware.egl=swiftshader",
+        "androidboot.hardware.hwcomposer=" + config.hwcomposer(),
+        "androidboot.hardware.egl=angle",
         "androidboot.hardware.vulkan=pastel",
     };
   }
 
-  if (gpu_mode == kGpuModeDrmVirgl) {
+  if (config.gpu_mode() == kGpuModeDrmVirgl) {
     return {
       "androidboot.cpuvulkan.version=0",
       "androidboot.hardware.gralloc=minigbm",
-      "androidboot.hardware.hwcomposer=drm_minigbm",
+      "androidboot.hardware.hwcomposer=drm",
       "androidboot.hardware.egl=mesa",
     };
   }
@@ -124,7 +156,8 @@
     case Arch::X86:
     case Arch::X86_64: {
       // QEMU has additional PCI devices for an ISA bridge and PIIX4
-      return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 2, num_disks);
+      // virtio_gpu precedes the first console or disk
+      return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 3, num_disks);
     }
     case Arch::Arm:
       return "androidboot.boot_devices=3f000000.pcie";
@@ -140,11 +173,13 @@
   auto stop = [](Subprocess* proc) {
     auto stopped = Stop();
     if (stopped) {
-      return true;
+      return StopperResult::kStopSuccess;
     }
     LOG(WARNING) << "Failed to stop VMM nicely, "
                   << "attempting to KILL";
-    return KillSubprocess(proc);
+    return KillSubprocess(proc) == StopperResult::kStopSuccess
+               ? StopperResult::kStopCrash
+               : StopperResult::kStopFailure;
   };
   std::string qemu_binary = config.qemu_binary_dir();
   switch (arch_) {
@@ -161,6 +196,8 @@
       qemu_binary += "/qemu-system-x86_64";
       break;
   }
+
+  auto qemu_version = GetQemuVersion(qemu_binary);
   Command qemu_cmd(qemu_binary, stop);
 
   int hvc_num = 0;
@@ -228,6 +265,7 @@
   };
 
   bool is_arm = arch_ == Arch::Arm || arch_ == Arch::Arm64;
+  bool is_arm64 = arch_ == Arch::Arm64;
 
   auto access_kregistry_size_bytes = 0;
   if (FileExists(instance.access_kregistry_path())) {
@@ -237,6 +275,14 @@
         << access_kregistry_size_bytes << ") not a multiple of 1MB";
   }
 
+  auto hwcomposer_pmem_size_bytes = 0;
+  if (FileExists(instance.hwcomposer_pmem_path())) {
+    hwcomposer_pmem_size_bytes = FileSize(instance.hwcomposer_pmem_path());
+    CHECK((hwcomposer_pmem_size_bytes & (1024 * 1024 - 1)) == 0)
+        << instance.hwcomposer_pmem_path() << " file size ("
+        << hwcomposer_pmem_size_bytes << ") not a multiple of 1MB";
+  }
+
   auto pstore_size_bytes = 0;
   if (FileExists(instance.pstore_path())) {
     pstore_size_bytes = FileSize(instance.pstore_path());
@@ -249,13 +295,28 @@
   qemu_cmd.AddParameter("guest=", instance.instance_name(), ",debug-threads=on");
 
   qemu_cmd.AddParameter("-machine");
-  auto machine = is_arm ? "virt,gic-version=2,mte=on"
-                        : "pc-i440fx-2.8,accel=kvm,nvdimm=on";
+  std::string machine = is_arm ? "virt" : "pc-i440fx-2.8,nvdimm=on";
+  if (IsHostCompatible(arch_)) {
+    machine += ",accel=kvm";
+    if (is_arm) {
+      machine += ",gic-version=3";
+    }
+  } else if (is_arm) {
+    // QEMU doesn't support GICv3 with TCG yet
+    machine += ",gic-version=2";
+    if (is_arm64) {
+      // Only enable MTE in TCG mode. We haven't started to run on ARMv8/ARMv9
+      // devices with KVM and MTE, so MTE will always require TCG
+      machine += ",mte=on";
+    }
+    CHECK(config.cpus() <= 8) << "CPUs must be no more than 8 with GICv2";
+  }
   qemu_cmd.AddParameter(machine, ",usb=off,dump-guest-core=off");
 
   qemu_cmd.AddParameter("-m");
   auto maxmem = config.memory_mb() +
-                access_kregistry_size_bytes / 1024 / 1024 +
+                (access_kregistry_size_bytes / 1024 / 1024) +
+                (hwcomposer_pmem_size_bytes / 1024 / 1024) +
                 (is_arm ? 0 : pstore_size_bytes / 1024 / 1024);
   auto slots = is_arm ? "" : ",slots=2";
   qemu_cmd.AddParameter("size=", config.memory_mb(), "M",
@@ -292,16 +353,40 @@
 
   qemu_cmd.AddParameter("-chardev");
   qemu_cmd.AddParameter("socket,id=charmonitor,path=", GetMonitorPath(config),
-                        ",server,nowait");
+                        ",server=on,wait=off");
 
   qemu_cmd.AddParameter("-mon");
   qemu_cmd.AddParameter("chardev=charmonitor,id=monitor,mode=control");
 
+  if (config.gpu_mode() == kGpuModeDrmVirgl) {
+    qemu_cmd.AddParameter("-display");
+    qemu_cmd.AddParameter("egl-headless");
+
+    qemu_cmd.AddParameter("-vnc");
+    qemu_cmd.AddParameter(":", instance.qemu_vnc_server_port());
+  } else {
+    qemu_cmd.AddParameter("-display");
+    qemu_cmd.AddParameter("none");
+  }
+
+  auto display_configs = config.display_configs();
+  CHECK_GE(display_configs.size(), 1);
+  auto display_config = display_configs[0];
+
+  qemu_cmd.AddParameter("-device");
+
+  bool use_gpu_gl = qemu_version.first >= 6 &&
+                    config.gpu_mode() != kGpuModeGuestSwiftshader;
+  qemu_cmd.AddParameter(use_gpu_gl ?
+                            "virtio-gpu-gl-pci" : "virtio-gpu-pci", ",id=gpu0",
+                        ",xres=", display_config.width,
+                        ",yres=", display_config.height);
+
   // In kgdb mode, earlycon is an interactive console, and so early
   // dmesg will go there instead of the kernel.log. On QEMU, we do this
   // bit of logic up before the hvc console is set up, so the command line
   // flags appear in the right order and "append=on" does the right thing
-  if (!(config.console() && (config.kgdb() || config.use_bootloader()))) {
+  if (!config.console() && (config.kgdb() || config.use_bootloader())) {
     add_serial_console_ro(instance.kernel_log_pipe_name());
   }
 
@@ -336,10 +421,6 @@
     add_hvc_sink();
   }
 
-  if (config.enable_gnss_grpc_proxy()) {
-    add_serial_console(instance.gnss_pipe_prefix());
-  }
-
   // Serial port for logcat, redirected to a pipe
   add_hvc_ro(instance.logcat_pipe_name());
 
@@ -351,6 +432,15 @@
     add_hvc_sink();
   }
 
+  if (config.enable_gnss_grpc_proxy()) {
+    add_hvc(instance.PerInstanceInternalPath("gnsshvc_fifo_vm"));
+    add_hvc(instance.PerInstanceInternalPath("locationhvc_fifo_vm"));
+  } else {
+    for (auto i = 0; i < 2; i++) {
+      add_hvc_sink();
+    }
+  }
+
   auto disk_num = instance.virtual_disk_paths().size();
 
   for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
@@ -378,23 +468,12 @@
                           ",id=virtio-disk", i, bootindex);
   }
 
-  if (config.gpu_mode() == kGpuModeDrmVirgl) {
-    qemu_cmd.AddParameter("-display");
-    qemu_cmd.AddParameter("egl-headless");
-
-    qemu_cmd.AddParameter("-vnc");
-    qemu_cmd.AddParameter(":", instance.vnc_server_port() - 5900);
-  } else {
-    qemu_cmd.AddParameter("-display");
-    qemu_cmd.AddParameter("none");
-  }
-
   if (!is_arm && FileExists(instance.pstore_path())) {
     // QEMU will assign the NVDIMM (ramoops pstore region) 100000000-1001fffff
     // As we will pass this to ramoops, define this region first so it is always
     // located at this address. This is currently x86 only.
     qemu_cmd.AddParameter("-object");
-    qemu_cmd.AddParameter("memory-backend-file,id=objpmem0,share,mem-path=",
+    qemu_cmd.AddParameter("memory-backend-file,id=objpmem0,share=on,mem-path=",
                           instance.pstore_path(), ",size=", pstore_size_bytes);
 
     qemu_cmd.AddParameter("-device");
@@ -403,14 +482,29 @@
 
   // QEMU does not implement virtio-pmem-pci for ARM64 yet; restore this
   // when the device has been added
-  if (!is_arm && FileExists(instance.access_kregistry_path())) {
-    qemu_cmd.AddParameter("-object");
-    qemu_cmd.AddParameter("memory-backend-file,id=objpmem1,share,mem-path=",
-                          instance.access_kregistry_path(), ",size=",
-                          access_kregistry_size_bytes);
+  if (!is_arm) {
+    if (access_kregistry_size_bytes > 0) {
+      qemu_cmd.AddParameter("-object");
+      qemu_cmd.AddParameter(
+          "memory-backend-file,id=objpmem1,share=on,mem-path=",
+          instance.access_kregistry_path(),
+          ",size=", access_kregistry_size_bytes);
 
-    qemu_cmd.AddParameter("-device");
-    qemu_cmd.AddParameter("virtio-pmem-pci,disable-legacy=on,memdev=objpmem1,id=pmem0");
+      qemu_cmd.AddParameter("-device");
+      qemu_cmd.AddParameter(
+          "virtio-pmem-pci,disable-legacy=on,memdev=objpmem1,id=pmem0");
+    }
+    if (hwcomposer_pmem_size_bytes > 0) {
+      qemu_cmd.AddParameter("-object");
+      qemu_cmd.AddParameter(
+          "memory-backend-file,id=objpmem2,share=on,mem-path=",
+          instance.hwcomposer_pmem_path(),
+          ",size=", hwcomposer_pmem_size_bytes);
+
+      qemu_cmd.AddParameter("-device");
+      qemu_cmd.AddParameter(
+          "virtio-pmem-pci,disable-legacy=on,memdev=objpmem2,id=pmem1");
+    }
   }
 
   qemu_cmd.AddParameter("-object");
@@ -421,10 +515,14 @@
                         "max-bytes=1024,period=2000");
 
   qemu_cmd.AddParameter("-device");
-  qemu_cmd.AddParameter("virtio-mouse-pci");
+  qemu_cmd.AddParameter("virtio-mouse-pci,disable-legacy=on");
 
   qemu_cmd.AddParameter("-device");
-  qemu_cmd.AddParameter("virtio-keyboard-pci");
+  qemu_cmd.AddParameter("virtio-keyboard-pci,disable-legacy=on");
+
+  // device padding for unsupported "switches" input
+  qemu_cmd.AddParameter("-device");
+  qemu_cmd.AddParameter("virtio-keyboard-pci,disable-legacy=on");
 
   auto vhost_net = config.vhost_net() ? ",vhost=on" : "";
 
@@ -444,16 +542,13 @@
 
   qemu_cmd.AddParameter("-device");
   qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet1,id=net1");
-
+#ifndef ENFORCE_MAC80211_HWSIM
   qemu_cmd.AddParameter("-netdev");
   qemu_cmd.AddParameter("tap,id=hostnet2,ifname=", instance.wifi_tap_name(),
                         ",script=no,downscript=no", vhost_net);
-
   qemu_cmd.AddParameter("-device");
   qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet2,id=net2");
-
-  qemu_cmd.AddParameter("-device");
-  qemu_cmd.AddParameter("virtio-gpu-pci,id=gpu0");
+#endif
 
   qemu_cmd.AddParameter("-cpu");
   qemu_cmd.AddParameter(IsHostCompatible(arch_) ? "host" : "max");
diff --git a/host/libs/vm_manager/qemu_manager.h b/host/libs/vm_manager/qemu_manager.h
index 934a976..f213ca2 100644
--- a/host/libs/vm_manager/qemu_manager.h
+++ b/host/libs/vm_manager/qemu_manager.h
@@ -31,15 +31,19 @@
  public:
   static std::string name() { return "qemu_cli"; }
 
-  QemuManager(Arch arch) : VmManager(arch) {}
+  QemuManager(Arch);
   virtual ~QemuManager() = default;
 
   bool IsSupported() override;
-  std::vector<std::string> ConfigureGpuMode(const std::string&) override;
+  std::vector<std::string> ConfigureGraphics(
+      const CuttlefishConfig& config) override;
   std::string ConfigureBootDevices(int num_disks) override;
 
   std::vector<cuttlefish::Command> StartCommands(
       const CuttlefishConfig& config) override;
+
+ private:
+  Arch arch_;
 };
 
 } // namespace vm_manager
diff --git a/host/libs/vm_manager/vm_manager.cpp b/host/libs/vm_manager/vm_manager.cpp
index 8c2ef57..2726f0c 100644
--- a/host/libs/vm_manager/vm_manager.cpp
+++ b/host/libs/vm_manager/vm_manager.cpp
@@ -17,12 +17,14 @@
 #include "host/libs/vm_manager/vm_manager.h"
 
 #include <android-base/logging.h>
+#include <fruit/fruit.h>
 
 #include <iomanip>
 #include <memory>
 
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
+#include "host/libs/vm_manager/gem5_manager.h"
 #include "host/libs/vm_manager/qemu_manager.h"
 
 namespace cuttlefish {
@@ -32,8 +34,10 @@
   std::unique_ptr<VmManager> vmm;
   if (name == QemuManager::name()) {
     vmm.reset(new QemuManager(arch));
+  } else if (name == Gem5Manager::name()) {
+    vmm.reset(new Gem5Manager(arch));
   } else if (name == CrosvmManager::name()) {
-    vmm.reset(new CrosvmManager(arch));
+    vmm.reset(new CrosvmManager());
   }
   if (!vmm) {
     LOG(ERROR) << "Invalid VM manager: " << name;
@@ -61,6 +65,16 @@
   return {boot_devices_prop};
 }
 
+fruit::Component<fruit::Required<const CuttlefishConfig>, VmManager>
+VmManagerComponent() {
+  return fruit::createComponent().registerProvider(
+      [](const CuttlefishConfig& config) {
+        auto vmm = GetVmManager(config.vm_manager(), config.target_arch());
+        CHECK(vmm) << "Invalid VMM/Arch: \"" << config.vm_manager() << "\""
+                   << (int)config.target_arch() << "\"";
+        return vmm.release();  // fruit takes ownership of raw pointers
+      });
+}
+
 } // namespace vm_manager
 } // namespace cuttlefish
-
diff --git a/host/libs/vm_manager/vm_manager.h b/host/libs/vm_manager/vm_manager.h
index b538e8f..7a81a97 100644
--- a/host/libs/vm_manager/vm_manager.h
+++ b/host/libs/vm_manager/vm_manager.h
@@ -14,21 +14,18 @@
  * limitations under the License.
  */
 #pragma once
+#include <common/libs/utils/subprocess.h>
+#include <fruit/fruit.h>
+#include <host/libs/config/cuttlefish_config.h>
 
 #include <string>
 #include <vector>
 
-#include <common/libs/utils/subprocess.h>
-#include <host/libs/config/cuttlefish_config.h>
-
 namespace cuttlefish {
 namespace vm_manager {
 
 // Superclass of every guest VM manager.
 class VmManager {
- protected:
-  const Arch arch_;
-
  public:
   // This is the number of HVC virtual console ports that should be configured
   // by the VmManager. Because crosvm currently allocates these ports as the
@@ -40,7 +37,7 @@
   // need to consume host resources, except for the PCI ID. Use this trick to
   // keep the number of PCI IDs assigned constant for all flags/vm manager
   // combinations
-  static const int kDefaultNumHvcs = 6;
+  static const int kDefaultNumHvcs = 8;
 
   // This is the number of virtual disks (block devices) that should be
   // configured by the VmManager. Related to the description above regarding
@@ -58,11 +55,11 @@
   // the persistent disk
   static const int kDefaultNumBootDevices = 2;
 
-  VmManager(Arch arch) : arch_(arch) {}
   virtual ~VmManager() = default;
 
   virtual bool IsSupported() = 0;
-  virtual std::vector<std::string> ConfigureGpuMode(const std::string&) = 0;
+  virtual std::vector<std::string> ConfigureGraphics(
+      const CuttlefishConfig& config) = 0;
   virtual std::string ConfigureBootDevices(int num_disks) = 0;
 
   // Starts the VMM. It will usually build a command and pass it to the
@@ -73,6 +70,9 @@
       const CuttlefishConfig& config) = 0;
 };
 
+fruit::Component<fruit::Required<const CuttlefishConfig>, VmManager>
+VmManagerComponent();
+
 std::unique_ptr<VmManager> GetVmManager(const std::string&, Arch arch);
 
 std::string ConfigureMultipleBootDevices(const std::string& pci_path, int pci_offset,
diff --git a/host/libs/web/Android.bp b/host/libs/web/Android.bp
new file mode 100644
index 0000000..a0bc146
--- /dev/null
+++ b/host/libs/web/Android.bp
@@ -0,0 +1,61 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+    name: "libcuttlefish_web",
+    srcs: [
+        "build_api.cc",
+        "credential_source.cc",
+        "curl_wrapper.cc",
+        "install_zip.cc",
+    ],
+    static_libs: [
+        "libcuttlefish_host_config",
+        "libext2_blkid",
+    ],
+    target: {
+        host: {
+            static_libs: [
+                "libbase",
+                "libcuttlefish_fs",
+                "libcuttlefish_utils",
+                "libcurl",
+                "libcrypto",
+                "liblog",
+                "libssl",
+                "libz",
+                "libjsoncpp",
+            ],
+        },
+        android: {
+            shared_libs: [
+                "libbase",
+                "libcuttlefish_fs",
+                "libcuttlefish_utils",
+                "libcurl",
+                "libcrypto",
+                "liblog",
+                "libssl",
+                "libz",
+                "libjsoncpp",
+            ],
+        },
+    },
+    defaults: ["cuttlefish_host"],
+}
diff --git a/host/libs/web/build_api.cc b/host/libs/web/build_api.cc
new file mode 100644
index 0000000..85d32bd
--- /dev/null
+++ b/host/libs/web/build_api.cc
@@ -0,0 +1,353 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "build_api.h"
+
+#include <dirent.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <set>
+#include <string>
+#include <thread>
+
+#include <android-base/strings.h>
+#include <android-base/logging.h>
+
+#include "common/libs/utils/environment.h"
+#include "common/libs/utils/files.h"
+
+namespace cuttlefish {
+namespace {
+
+const std::string BUILD_API =
+    "https://www.googleapis.com/android/internal/build/v3";
+
+bool StatusIsTerminal(const std::string& status) {
+  const static std::set<std::string> terminal_statuses = {
+    "abandoned",
+    "complete",
+    "error",
+    "ABANDONED",
+    "COMPLETE",
+    "ERROR",
+  };
+  return terminal_statuses.count(status) > 0;
+}
+
+} // namespace
+
+Artifact::Artifact(const Json::Value& json_artifact) {
+  name = json_artifact["name"].asString();
+  size = std::stol(json_artifact["size"].asString());
+  last_modified_time = std::stol(json_artifact["lastModifiedTime"].asString());
+  md5 = json_artifact["md5"].asString();
+  content_type = json_artifact["contentType"].asString();
+  revision = json_artifact["revision"].asString();
+  creation_time = std::stol(json_artifact["creationTime"].asString());
+  crc32 = json_artifact["crc32"].asUInt();
+}
+
+std::ostream& operator<<(std::ostream& out, const DeviceBuild& build) {
+  return out << "(id=\"" << build.id << "\", target=\"" << build.target << "\")";
+}
+
+std::ostream& operator<<(std::ostream& out, const DirectoryBuild& build) {
+  auto paths = android::base::Join(build.paths, ":");
+  return out << "(paths=\"" << paths << "\", target=\"" << build.target << "\")";
+}
+
+std::ostream& operator<<(std::ostream& out, const Build& build) {
+  std::visit([&out](auto&& arg) { out << arg; }, build);
+  return out;
+}
+
+DirectoryBuild::DirectoryBuild(const std::vector<std::string>& paths,
+                               const std::string& target)
+    : paths(paths), target(target), id("eng") {
+  product = StringFromEnv("TARGET_PRODUCT", "");
+}
+
+BuildApi::BuildApi(CurlWrapper& curl, CredentialSource* credential_source)
+    : BuildApi(curl, credential_source, "") {}
+
+BuildApi::BuildApi(CurlWrapper& curl, CredentialSource* credential_source,
+                   std::string api_key)
+    : curl(curl),
+      credential_source(credential_source),
+      api_key_(std::move(api_key)) {}
+
+std::vector<std::string> BuildApi::Headers() {
+  std::vector<std::string> headers;
+  if (credential_source) {
+    headers.push_back("Authorization: Bearer " +
+                      credential_source->Credential());
+  }
+  return headers;
+}
+
+std::string BuildApi::LatestBuildId(const std::string& branch,
+                                    const std::string& target) {
+  std::string url =
+      BUILD_API + "/builds?branch=" + curl.UrlEscape(branch) +
+      "&buildAttemptStatus=complete" +
+      "&buildType=submitted&maxResults=1&successful=true&target=" +
+      curl.UrlEscape(target);
+  if (!api_key_.empty()) {
+    url += "&key=" + curl.UrlEscape(api_key_);
+  }
+  auto curl_response = curl.DownloadToJson(url, Headers());
+  const auto& json = curl_response.data;
+  if (!curl_response.HttpSuccess()) {
+    LOG(FATAL) << "Error fetching the latest build of \"" << target
+               << "\" on \"" << branch << "\". The server response was \""
+               << json << "\", and code was " << curl_response.http_code;
+  }
+  CHECK(!json.isMember("error"))
+      << "Response had \"error\" but had http success status. Received \""
+      << json << "\"";
+
+  if (!json.isMember("builds") || json["builds"].size() != 1) {
+    LOG(WARNING) << "expected to receive 1 build for \"" << target << "\" on \""
+                 << branch << "\", but received " << json["builds"].size()
+                 << ". Full response was " << json;
+    return "";
+  }
+  return json["builds"][0]["buildId"].asString();
+}
+
+std::string BuildApi::BuildStatus(const DeviceBuild& build) {
+  std::string url = BUILD_API + "/builds/" + curl.UrlEscape(build.id) + "/" +
+                    curl.UrlEscape(build.target);
+  if (!api_key_.empty()) {
+    url += "?key=" + curl.UrlEscape(api_key_);
+  }
+  auto curl_response = curl.DownloadToJson(url, Headers());
+  const auto& json = curl_response.data;
+  if (!curl_response.HttpSuccess()) {
+    LOG(FATAL) << "Error fetching the status of \"" << build
+               << "\". The server response was \"" << json
+               << "\", and code was " << curl_response.http_code;
+  }
+  CHECK(!json.isMember("error"))
+      << "Response had \"error\" but had http success status. Received \""
+      << json << "\"";
+
+  return json["buildAttemptStatus"].asString();
+}
+
+std::string BuildApi::ProductName(const DeviceBuild& build) {
+  std::string url = BUILD_API + "/builds/" + curl.UrlEscape(build.id) + "/" +
+                    curl.UrlEscape(build.target);
+  if (!api_key_.empty()) {
+    url += "?key=" + curl.UrlEscape(api_key_);
+  }
+  auto curl_response = curl.DownloadToJson(url, Headers());
+  const auto& json = curl_response.data;
+  if (!curl_response.HttpSuccess()) {
+    LOG(FATAL) << "Error fetching the product name of \"" << build
+               << "\". The server response was \"" << json
+               << "\", and code was " << curl_response.http_code;
+  }
+  CHECK(!json.isMember("error"))
+      << "Response had \"error\" but had http success status. Received \""
+      << json << "\"";
+
+  CHECK(json.isMember("target")) << "Build was missing target field.";
+  return json["target"]["product"].asString();
+}
+
+std::vector<Artifact> BuildApi::Artifacts(const DeviceBuild& build) {
+  std::string page_token = "";
+  std::vector<Artifact> artifacts;
+  do {
+    std::string url = BUILD_API + "/builds/" + curl.UrlEscape(build.id) + "/" +
+                      curl.UrlEscape(build.target) +
+                      "/attempts/latest/artifacts?maxResults=100";
+    if (page_token != "") {
+      url += "&pageToken=" + curl.UrlEscape(page_token);
+    }
+    if (!api_key_.empty()) {
+      url += "&key=" + curl.UrlEscape(api_key_);
+    }
+    auto curl_response = curl.DownloadToJson(url, Headers());
+    const auto& json = curl_response.data;
+    if (!curl_response.HttpSuccess()) {
+      LOG(FATAL) << "Error fetching the artifacts of \"" << build
+                 << "\". The server response was \"" << json
+                 << "\", and code was " << curl_response.http_code;
+    }
+    CHECK(!json.isMember("error"))
+        << "Response had \"error\" but had http success status. Received \""
+        << json << "\"";
+    if (json.isMember("nextPageToken")) {
+      page_token = json["nextPageToken"].asString();
+    } else {
+      page_token = "";
+    }
+    for (const auto& artifact_json : json["artifacts"]) {
+      artifacts.emplace_back(artifact_json);
+    }
+  } while (page_token != "");
+  return artifacts;
+}
+
+struct CloseDir {
+  void operator()(DIR* dir) {
+    closedir(dir);
+  }
+};
+
+using UniqueDir = std::unique_ptr<DIR, CloseDir>;
+
+std::vector<Artifact> BuildApi::Artifacts(const DirectoryBuild& build) {
+  std::vector<Artifact> artifacts;
+  for (const auto& path : build.paths) {
+    auto dir = UniqueDir(opendir(path.c_str()));
+    CHECK(dir != nullptr) << "Could not read files from \"" << path << "\"";
+    for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
+      artifacts.emplace_back(std::string(entity->d_name));
+    }
+  }
+  return artifacts;
+}
+
+bool BuildApi::ArtifactToCallback(const DeviceBuild& build,
+                                  const std::string& artifact,
+                                  CurlWrapper::DataCallback callback) {
+  std::string download_url_endpoint =
+      BUILD_API + "/builds/" + curl.UrlEscape(build.id) + "/" +
+      curl.UrlEscape(build.target) + "/attempts/latest/artifacts/" +
+      curl.UrlEscape(artifact) + "/url";
+  if (!api_key_.empty()) {
+    download_url_endpoint += "?key=" + curl.UrlEscape(api_key_);
+  }
+  auto curl_response = curl.DownloadToJson(download_url_endpoint, Headers());
+  const auto& json = curl_response.data;
+  if (!(curl_response.HttpSuccess() || curl_response.HttpRedirect())) {
+    LOG(ERROR) << "Error fetching the url of \"" << artifact << "\" for \""
+               << build << "\". The server response was \"" << json
+               << "\", and code was " << curl_response.http_code;
+    return false;
+  }
+  if (json.isMember("error")) {
+    LOG(ERROR) << "Response had \"error\" but had http success status. "
+               << "Received \"" << json << "\"";
+    return false;
+  }
+  if (!json.isMember("signedUrl")) {
+    LOG(ERROR) << "URL endpoint did not have json path: " << json;
+    return false;
+  }
+  std::string url = json["signedUrl"].asString();
+  return curl.DownloadToCallback(callback, url).HttpSuccess();
+}
+
+bool BuildApi::ArtifactToFile(const DeviceBuild& build,
+                              const std::string& artifact,
+                              const std::string& path) {
+  std::string download_url_endpoint =
+      BUILD_API + "/builds/" + curl.UrlEscape(build.id) + "/" +
+      curl.UrlEscape(build.target) + "/attempts/latest/artifacts/" +
+      curl.UrlEscape(artifact) + "/url";
+  if (!api_key_.empty()) {
+    download_url_endpoint += "?key=" + curl.UrlEscape(api_key_);
+  }
+  auto curl_response = curl.DownloadToJson(download_url_endpoint, Headers());
+  const auto& json = curl_response.data;
+  if (!(curl_response.HttpSuccess() || curl_response.HttpRedirect())) {
+    LOG(ERROR) << "Error fetching the url of \"" << artifact << "\" for \""
+               << build << "\". The server response was \"" << json
+               << "\", and code was " << curl_response.http_code;
+    return false;
+  }
+  if (json.isMember("error")) {
+    LOG(ERROR) << "Response had \"error\" but had http success status. "
+               << "Received \"" << json << "\"";
+  }
+  if (!json.isMember("signedUrl")) {
+    LOG(ERROR) << "URL endpoint did not have json path: " << json;
+    return false;
+  }
+  std::string url = json["signedUrl"].asString();
+  return curl.DownloadToFile(url, path).HttpSuccess();
+}
+
+bool BuildApi::ArtifactToFile(const DirectoryBuild& build,
+                              const std::string& artifact,
+                              const std::string& destination) {
+  for (const auto& path : build.paths) {
+    auto source = path + "/" + artifact;
+    if (!FileExists(source)) {
+      continue;
+    }
+    unlink(destination.c_str());
+    if (symlink(source.c_str(), destination.c_str())) {
+      int error_num = errno;
+      LOG(ERROR) << "Could not create symlink from " << source << " to "
+                  << destination << ": " << strerror(error_num);
+      return false;
+    }
+    return true;
+  }
+  return false;
+}
+
+Build ArgumentToBuild(BuildApi* build_api, const std::string& arg,
+                      const std::string& default_build_target,
+                      const std::chrono::seconds& retry_period) {
+  if (arg.find(':') != std::string::npos) {
+    std::vector<std::string> dirs = android::base::Split(arg, ":");
+    std::string id = dirs.back();
+    dirs.pop_back();
+    return DirectoryBuild(dirs, id);
+  }
+  size_t slash_pos = arg.find('/');
+  if (slash_pos != std::string::npos
+        && arg.find('/', slash_pos + 1) != std::string::npos) {
+    LOG(FATAL) << "Build argument cannot have more than one '/' slash. Was at "
+        << slash_pos << " and " << arg.find('/', slash_pos + 1);
+  }
+  std::string build_target = slash_pos == std::string::npos
+      ? default_build_target : arg.substr(slash_pos + 1);
+  std::string branch_or_id = slash_pos == std::string::npos
+      ? arg: arg.substr(0, slash_pos);
+  std::string branch_latest_build_id =
+      build_api->LatestBuildId(branch_or_id, build_target);
+  std::string build_id = branch_or_id;
+  if (branch_latest_build_id != "") {
+    LOG(INFO) << "The latest good build on branch \"" << branch_or_id
+        << "\"with build target \"" << build_target
+        << "\" is \"" << branch_latest_build_id << "\"";
+    build_id = branch_latest_build_id;
+  }
+  DeviceBuild proposed_build = DeviceBuild(build_id, build_target);
+  std::string status = build_api->BuildStatus(proposed_build);
+  if (status == "") {
+    LOG(FATAL) << proposed_build << " is not a valid branch or build id.";
+  }
+  LOG(INFO) << "Status for build " << proposed_build << " is " << status;
+  while (retry_period != std::chrono::seconds::zero() && !StatusIsTerminal(status)) {
+    LOG(INFO) << "Status is \"" << status << "\". Waiting for " << retry_period.count()
+        << " seconds.";
+    std::this_thread::sleep_for(retry_period);
+    status = build_api->BuildStatus(proposed_build);
+  }
+  LOG(INFO) << "Status for build " << proposed_build << " is " << status;
+  proposed_build.product = build_api->ProductName(proposed_build);
+  return proposed_build;
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/fetcher/build_api.h b/host/libs/web/build_api.h
similarity index 90%
rename from host/commands/fetcher/build_api.h
rename to host/libs/web/build_api.h
index 505ad4b..7a78389 100644
--- a/host/commands/fetcher/build_api.h
+++ b/host/libs/web/build_api.h
@@ -81,12 +81,9 @@
 std::ostream& operator<<(std::ostream&, const Build&);
 
 class BuildApi {
-  CurlWrapper curl;
-  std::unique_ptr<CredentialSource> credential_source;
-
-  std::vector<std::string> Headers();
-public:
-  BuildApi(std::unique_ptr<CredentialSource> credential_source);
+ public:
+  BuildApi(CurlWrapper&, CredentialSource*);
+  BuildApi(CurlWrapper&, CredentialSource*, std::string api_key);
   ~BuildApi() = default;
 
   std::string LatestBuildId(const std::string& branch,
@@ -98,6 +95,9 @@
 
   std::vector<Artifact> Artifacts(const DeviceBuild&);
 
+  bool ArtifactToCallback(const DeviceBuild& build, const std::string& artifact,
+                          CurlWrapper::DataCallback callback);
+
   bool ArtifactToFile(const DeviceBuild& build, const std::string& artifact,
                       const std::string& path);
 
@@ -116,6 +116,13 @@
       return ArtifactToFile(arg, artifact, path);
     }, build);
   }
+
+ private:
+  std::vector<std::string> Headers();
+
+  CurlWrapper& curl;
+  CredentialSource* credential_source;
+  std::string api_key_;
 };
 
 Build ArgumentToBuild(BuildApi* api, const std::string& arg,
diff --git a/host/libs/web/credential_source.cc b/host/libs/web/credential_source.cc
new file mode 100644
index 0000000..c12c494
--- /dev/null
+++ b/host/libs/web/credential_source.cc
@@ -0,0 +1,313 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "credential_source.h"
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <json/json.h>
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+
+#include "common/libs/utils/base64.h"
+
+namespace cuttlefish {
+namespace {
+
+std::chrono::steady_clock::duration REFRESH_WINDOW =
+    std::chrono::minutes(2);
+std::string REFRESH_URL = "http://metadata.google.internal/computeMetadata/"
+    "v1/instance/service-accounts/default/token";
+
+} // namespace
+
+GceMetadataCredentialSource::GceMetadataCredentialSource(CurlWrapper& curl)
+    : curl(curl) {
+  latest_credential = "";
+  expiration = std::chrono::steady_clock::now();
+}
+
+std::string GceMetadataCredentialSource::Credential() {
+  if (expiration - std::chrono::steady_clock::now() < REFRESH_WINDOW) {
+    RefreshCredential();
+  }
+  return latest_credential;
+}
+
+void GceMetadataCredentialSource::RefreshCredential() {
+  auto curl_response =
+      curl.DownloadToJson(REFRESH_URL, {"Metadata-Flavor: Google"});
+  const auto& json = curl_response.data;
+  if (!curl_response.HttpSuccess()) {
+    LOG(FATAL) << "Error fetching credentials. The server response was \""
+               << json << "\", and code was " << curl_response.http_code;
+  }
+  CHECK(!json.isMember("error"))
+      << "Response had \"error\" but had http success status. Received \""
+      << json << "\"";
+
+  bool has_access_token = json.isMember("access_token");
+  bool has_expires_in = json.isMember("expires_in");
+  if (!has_access_token || !has_expires_in) {
+    LOG(FATAL) << "GCE credential was missing access_token or expires_in. "
+               << "Full response was " << json << "";
+  }
+
+  expiration = std::chrono::steady_clock::now() +
+               std::chrono::seconds(json["expires_in"].asInt());
+  latest_credential = json["access_token"].asString();
+}
+
+std::unique_ptr<CredentialSource> GceMetadataCredentialSource::make(
+    CurlWrapper& curl) {
+  return std::unique_ptr<CredentialSource>(
+      new GceMetadataCredentialSource(curl));
+}
+
+FixedCredentialSource::FixedCredentialSource(const std::string& credential) {
+  this->credential = credential;
+}
+
+std::string FixedCredentialSource::Credential() {
+  return credential;
+}
+
+std::unique_ptr<CredentialSource> FixedCredentialSource::make(
+    const std::string& credential) {
+  return std::unique_ptr<CredentialSource>(new FixedCredentialSource(credential));
+}
+
+Result<RefreshCredentialSource> RefreshCredentialSource::FromOauth2ClientFile(
+    CurlWrapper& curl, std::istream& stream) {
+  Json::CharReaderBuilder builder;
+  std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+  Json::Value json;
+  std::string errorMessage;
+  CF_EXPECT(Json::parseFromStream(builder, stream, &json, &errorMessage),
+            "Failed to parse json: " << errorMessage);
+  CF_EXPECT(json.isMember("data"));
+  auto& data = json["data"];
+  CF_EXPECT(data.type() == Json::ValueType::arrayValue);
+
+  CF_EXPECT(data.size() == 1);
+  auto& data_first = data[0];
+  CF_EXPECT(data_first.type() == Json::ValueType::objectValue);
+
+  CF_EXPECT(data_first.isMember("credential"));
+  auto& credential = data_first["credential"];
+  CF_EXPECT(credential.type() == Json::ValueType::objectValue);
+
+  CF_EXPECT(credential.isMember("client_id"));
+  auto& client_id = credential["client_id"];
+  CF_EXPECT(client_id.type() == Json::ValueType::stringValue);
+
+  CF_EXPECT(credential.isMember("client_secret"));
+  auto& client_secret = credential["client_secret"];
+  CF_EXPECT(client_secret.type() == Json::ValueType::stringValue);
+
+  CF_EXPECT(credential.isMember("token_response"));
+  auto& token_response = credential["token_response"];
+  CF_EXPECT(token_response.type() == Json::ValueType::objectValue);
+
+  CF_EXPECT(token_response.isMember("refresh_token"));
+  auto& refresh_token = credential["refresh_token"];
+  CF_EXPECT(refresh_token.type() == Json::ValueType::stringValue);
+
+  return RefreshCredentialSource(curl, client_id.asString(),
+                                 client_secret.asString(),
+                                 refresh_token.asString());
+}
+
+RefreshCredentialSource::RefreshCredentialSource(
+    CurlWrapper& curl, const std::string& client_id,
+    const std::string& client_secret, const std::string& refresh_token)
+    : curl_(curl),
+      client_id_(client_id),
+      client_secret_(client_secret),
+      refresh_token_(refresh_token) {}
+
+std::string RefreshCredentialSource::Credential() {
+  if (expiration_ - std::chrono::steady_clock::now() < REFRESH_WINDOW) {
+    UpdateLatestCredential();
+  }
+  return latest_credential_;
+}
+
+void RefreshCredentialSource::UpdateLatestCredential() {
+  std::vector<std::string> headers = {
+      "Content-Type: application/x-www-form-urlencoded"};
+  std::stringstream data;
+  data << "client_id=" << curl_.UrlEscape(client_id_) << "&";
+  data << "client_secret=" << curl_.UrlEscape(client_secret_) << "&";
+  data << "refresh_token=" << curl_.UrlEscape(refresh_token_) << "&";
+  data << "grant_type=refresh_token";
+
+  static constexpr char kUrl[] = "https://oauth2.googleapis.com/token";
+  auto response = curl_.PostToJson(kUrl, data.str(), headers);
+  CHECK(response.HttpSuccess()) << response.data;
+  auto& json = response.data;
+
+  CHECK(!json.isMember("error"))
+      << "Response had \"error\" but had http success status. Received \""
+      << json << "\"";
+
+  bool has_access_token = json.isMember("access_token");
+  bool has_expires_in = json.isMember("expires_in");
+  CHECK(has_access_token && has_expires_in)
+      << "GCE credential was missing access_token or expires_in. "
+      << "Full response was " << json << "";
+
+  expiration_ = std::chrono::steady_clock::now() +
+                std::chrono::seconds(json["expires_in"].asInt());
+  latest_credential_ = json["access_token"].asString();
+}
+
+static std::string CollectSslErrors() {
+  std::stringstream errors;
+  auto callback = [](const char* str, size_t len, void* stream) {
+    ((std::stringstream*)stream)->write(str, len);
+    return 1;  // success
+  };
+  ERR_print_errors_cb(callback, &errors);
+  return errors.str();
+}
+
+Result<ServiceAccountOauthCredentialSource>
+ServiceAccountOauthCredentialSource::FromJson(CurlWrapper& curl,
+                                              const Json::Value& json,
+                                              const std::string& scope) {
+  ServiceAccountOauthCredentialSource source(curl);
+  source.scope_ = scope;
+
+  CF_EXPECT(json.isMember("client_email"));
+  CF_EXPECT(json["client_email"].type() == Json::ValueType::stringValue);
+  source.email_ = json["client_email"].asString();
+
+  CF_EXPECT(json.isMember("private_key"));
+  CF_EXPECT(json["private_key"].type() == Json::ValueType::stringValue);
+  std::string key_str = json["private_key"].asString();
+
+  std::unique_ptr<BIO, int (*)(BIO*)> bo(CF_EXPECT(BIO_new(BIO_s_mem())),
+                                         BIO_free);
+  CF_EXPECT(BIO_write(bo.get(), key_str.c_str(), key_str.size()) ==
+            key_str.size());
+
+  auto pkey = CF_EXPECT(PEM_read_bio_PrivateKey(bo.get(), nullptr, 0, 0),
+                        CollectSslErrors());
+  source.private_key_.reset(pkey);
+
+  return source;
+}
+
+ServiceAccountOauthCredentialSource::ServiceAccountOauthCredentialSource(
+    CurlWrapper& curl)
+    : curl_(curl), private_key_(nullptr, EVP_PKEY_free) {}
+
+static std::string Base64Url(const char* data, std::size_t size) {
+  std::string base64;
+  CHECK(EncodeBase64(data, size, &base64));
+  base64 = android::base::StringReplace(base64, "+", "-", /* all */ true);
+  base64 = android::base::StringReplace(base64, "/", "_", /* all */ true);
+  return base64;
+}
+
+static std::string JsonToBase64Url(const Json::Value& json) {
+  Json::StreamWriterBuilder factory;
+  auto serialized = Json::writeString(factory, json);
+  return Base64Url(serialized.c_str(), serialized.size());
+}
+
+static std::string CreateJwt(const std::string& email, const std::string& scope,
+                             EVP_PKEY* private_key) {
+  using std::chrono::duration_cast;
+  using std::chrono::minutes;
+  using std::chrono::seconds;
+  using std::chrono::system_clock;
+  // https://developers.google.com/identity/protocols/oauth2/service-account
+  Json::Value header_json;
+  header_json["alg"] = "RS256";
+  header_json["typ"] = "JWT";
+  std::string header_str = JsonToBase64Url(header_json);
+
+  Json::Value claim_set_json;
+  claim_set_json["iss"] = email;
+  claim_set_json["scope"] = scope;
+  claim_set_json["aud"] = "https://oauth2.googleapis.com/token";
+  auto time = system_clock::now();
+  claim_set_json["iat"] =
+      (uint64_t)duration_cast<seconds>(time.time_since_epoch()).count();
+  auto exp = time + minutes(30);
+  claim_set_json["exp"] =
+      (uint64_t)duration_cast<seconds>(exp.time_since_epoch()).count();
+  std::string claim_set_str = JsonToBase64Url(claim_set_json);
+
+  std::string jwt_to_sign = header_str + "." + claim_set_str;
+
+  std::unique_ptr<EVP_MD_CTX, void (*)(EVP_MD_CTX*)> sign_ctx(
+      EVP_MD_CTX_create(), EVP_MD_CTX_free);
+  CHECK(EVP_DigestSignInit(sign_ctx.get(), nullptr, EVP_sha256(), nullptr,
+                           private_key));
+  CHECK(EVP_DigestSignUpdate(sign_ctx.get(), jwt_to_sign.c_str(),
+                             jwt_to_sign.size()));
+  size_t length;
+  CHECK(EVP_DigestSignFinal(sign_ctx.get(), nullptr, &length));
+  std::vector<uint8_t> sig_raw(length);
+  CHECK(EVP_DigestSignFinal(sign_ctx.get(), sig_raw.data(), &length));
+
+  return jwt_to_sign + "." + Base64Url((const char*)sig_raw.data(), length);
+}
+
+void ServiceAccountOauthCredentialSource::RefreshCredential() {
+  static constexpr char URL[] = "https://oauth2.googleapis.com/token";
+  static constexpr char GRANT[] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
+  std::stringstream content;
+  content << "grant_type=" << curl_.UrlEscape(GRANT) << "&";
+  auto jwt = CreateJwt(email_, scope_, private_key_.get());
+  content << "assertion=" << curl_.UrlEscape(jwt);
+  std::vector<std::string> headers = {
+      "Content-Type: application/x-www-form-urlencoded"};
+  auto curl_response = curl_.PostToJson(URL, content.str(), headers);
+  if (!curl_response.HttpSuccess()) {
+    LOG(FATAL) << "Error fetching credentials. The server response was \""
+               << curl_response.data << "\", and code was "
+               << curl_response.http_code;
+  }
+  Json::Value json = curl_response.data;
+
+  CHECK(!json.isMember("error"))
+      << "Response had \"error\" but had http success status. Received \""
+      << json << "\"";
+
+  bool has_access_token = json.isMember("access_token");
+  bool has_expires_in = json.isMember("expires_in");
+  if (!has_access_token || !has_expires_in) {
+    LOG(FATAL) << "GCE credential was missing access_token or expires_in. "
+               << "Full response was " << json << "";
+  }
+
+  expiration_ = std::chrono::steady_clock::now() +
+                std::chrono::seconds(json["expires_in"].asInt());
+  latest_credential_ = json["access_token"].asString();
+}
+
+std::string ServiceAccountOauthCredentialSource::Credential() {
+  if (expiration_ - std::chrono::steady_clock::now() < REFRESH_WINDOW) {
+    RefreshCredential();
+  }
+  return latest_credential_;
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/web/credential_source.h b/host/libs/web/credential_source.h
new file mode 100644
index 0000000..7ab64e1
--- /dev/null
+++ b/host/libs/web/credential_source.h
@@ -0,0 +1,105 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+
+#include <json/json.h>
+#include <openssl/evp.h>
+
+#include "common/libs/utils/result.h"
+#include "host/libs/web/curl_wrapper.h"
+
+namespace cuttlefish {
+
+class CredentialSource {
+public:
+  virtual ~CredentialSource() = default;
+  virtual std::string Credential() = 0;
+};
+
+class GceMetadataCredentialSource : public CredentialSource {
+  CurlWrapper& curl;
+  std::string latest_credential;
+  std::chrono::steady_clock::time_point expiration;
+
+  void RefreshCredential();
+public:
+ GceMetadataCredentialSource(CurlWrapper&);
+ GceMetadataCredentialSource(GceMetadataCredentialSource&&) = default;
+
+ virtual std::string Credential();
+
+ static std::unique_ptr<CredentialSource> make(CurlWrapper&);
+};
+
+class FixedCredentialSource : public CredentialSource {
+  std::string credential;
+public:
+  FixedCredentialSource(const std::string& credential);
+
+  virtual std::string Credential();
+
+  static std::unique_ptr<CredentialSource> make(const std::string& credential);
+};
+
+class RefreshCredentialSource : public CredentialSource {
+ public:
+  static Result<RefreshCredentialSource> FromOauth2ClientFile(
+      CurlWrapper& curl, std::istream& stream);
+
+  RefreshCredentialSource(CurlWrapper& curl, const std::string& client_id,
+                          const std::string& client_secret,
+                          const std::string& refresh_token);
+
+  std::string Credential() override;
+
+ private:
+  void UpdateLatestCredential();
+
+  CurlWrapper& curl_;
+  std::string client_id_;
+  std::string client_secret_;
+  std::string refresh_token_;
+
+  std::string latest_credential_;
+  std::chrono::steady_clock::time_point expiration_;
+};
+
+class ServiceAccountOauthCredentialSource : public CredentialSource {
+ public:
+  static Result<ServiceAccountOauthCredentialSource> FromJson(
+      CurlWrapper& curl, const Json::Value& service_account_json,
+      const std::string& scope);
+  ServiceAccountOauthCredentialSource(ServiceAccountOauthCredentialSource&&) =
+      default;
+
+  std::string Credential() override;
+
+ private:
+  ServiceAccountOauthCredentialSource(CurlWrapper& curl);
+  void RefreshCredential();
+
+  CurlWrapper& curl_;
+  std::string email_;
+  std::string scope_;
+  std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)> private_key_;
+
+  std::string latest_credential_;
+  std::chrono::steady_clock::time_point expiration_;
+};
+}
diff --git a/host/libs/web/curl_wrapper.cc b/host/libs/web/curl_wrapper.cc
new file mode 100644
index 0000000..b81d498
--- /dev/null
+++ b/host/libs/web/curl_wrapper.cc
@@ -0,0 +1,403 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/libs/web/curl_wrapper.h"
+
+#include <stdio.h>
+
+#include <fstream>
+#include <mutex>
+#include <sstream>
+#include <string>
+#include <thread>
+
+#include <android-base/logging.h>
+#include <curl/curl.h>
+#include <json/json.h>
+
+namespace cuttlefish {
+namespace {
+
+size_t curl_to_function_cb(char* ptr, size_t, size_t nmemb, void* userdata) {
+  CurlWrapper::DataCallback* callback = (CurlWrapper::DataCallback*)userdata;
+  if (!(*callback)(ptr, nmemb)) {
+    return 0;  // Signals error to curl
+  }
+  return nmemb;
+}
+
+size_t file_write_callback(char *ptr, size_t, size_t nmemb, void *userdata) {
+  std::stringstream* stream = (std::stringstream*) userdata;
+  stream->write(ptr, nmemb);
+  return nmemb;
+}
+
+curl_slist* build_slist(const std::vector<std::string>& strings) {
+  curl_slist* curl_headers = nullptr;
+  for (const auto& str : strings) {
+    curl_slist* temp = curl_slist_append(curl_headers, str.c_str());
+    if (temp == nullptr) {
+      LOG(ERROR) << "curl_slist_append failed to add " << str;
+      if (curl_headers) {
+        curl_slist_free_all(curl_headers);
+        return nullptr;
+      }
+    }
+    curl_headers = temp;
+  }
+  return curl_headers;
+}
+
+class CurlWrapperImpl : public CurlWrapper {
+ public:
+  CurlWrapperImpl() {
+    curl_ = curl_easy_init();
+    if (!curl_) {
+      LOG(ERROR) << "failed to initialize curl";
+      return;
+    }
+  }
+  ~CurlWrapperImpl() { curl_easy_cleanup(curl_); }
+
+  CurlResponse<std::string> PostToString(
+      const std::string& url, const std::string& data_to_write,
+      const std::vector<std::string>& headers) override {
+    std::lock_guard<std::mutex> lock(mutex_);
+    LOG(INFO) << "Attempting to download \"" << url << "\"";
+    if (!curl_) {
+      LOG(ERROR) << "curl was not initialized\n";
+      return {"", -1};
+    }
+    curl_slist* curl_headers = build_slist(headers);
+    curl_easy_reset(curl_);
+    curl_easy_setopt(curl_, CURLOPT_CAINFO,
+                     "/etc/ssl/certs/ca-certificates.crt");
+    curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers);
+    curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
+    curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, data_to_write.size());
+    curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, data_to_write.c_str());
+    std::stringstream data_to_read;
+    curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, file_write_callback);
+    curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &data_to_read);
+    char error_buf[CURL_ERROR_SIZE];
+    curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf);
+    curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L);
+    CURLcode res = curl_easy_perform(curl_);
+    if (curl_headers) {
+      curl_slist_free_all(curl_headers);
+    }
+    if (res != CURLE_OK) {
+      LOG(ERROR) << "curl_easy_perform() failed. "
+                 << "Code was \"" << res << "\". "
+                 << "Strerror was \"" << curl_easy_strerror(res) << "\". "
+                 << "Error buffer was \"" << error_buf << "\".";
+      return {"", -1};
+    }
+    long http_code = 0;
+    curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
+    return {data_to_read.str(), http_code};
+  }
+
+  CurlResponse<Json::Value> PostToJson(
+      const std::string& url, const std::string& data_to_write,
+      const std::vector<std::string>& headers) override {
+    CurlResponse<std::string> response =
+        PostToString(url, data_to_write, headers);
+    const std::string& contents = response.data;
+    Json::CharReaderBuilder builder;
+    std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+    Json::Value json;
+    std::string errorMessage;
+    if (!reader->parse(&*contents.begin(), &*contents.end(), &json,
+                       &errorMessage)) {
+      LOG(ERROR) << "Could not parse json: " << errorMessage;
+      json["error"] = "Failed to parse json.";
+      json["response"] = contents;
+    }
+    return {json, response.http_code};
+  }
+
+  CurlResponse<Json::Value> PostToJson(
+      const std::string& url, const Json::Value& data_to_write,
+      const std::vector<std::string>& headers) override {
+    std::stringstream json_str;
+    json_str << data_to_write;
+    return PostToJson(url, json_str.str(), headers);
+  }
+
+  CurlResponse<bool> DownloadToCallback(
+      DataCallback callback, const std::string& url,
+      const std::vector<std::string>& headers) {
+    std::lock_guard<std::mutex> lock(mutex_);
+    LOG(INFO) << "Attempting to download \"" << url << "\"";
+    if (!curl_) {
+      LOG(ERROR) << "curl was not initialized\n";
+      return {false, -1};
+    }
+    if (!callback(nullptr, 0)) {  // Signal start of data
+      LOG(ERROR) << "Callback failure\n";
+      return {false, -1};
+    }
+    curl_slist* curl_headers = build_slist(headers);
+    curl_easy_reset(curl_);
+    curl_easy_setopt(curl_, CURLOPT_CAINFO,
+                     "/etc/ssl/certs/ca-certificates.crt");
+    curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers);
+    curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
+    curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, curl_to_function_cb);
+    curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &callback);
+    char error_buf[CURL_ERROR_SIZE];
+    curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf);
+    curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L);
+    CURLcode res = curl_easy_perform(curl_);
+    if (curl_headers) {
+      curl_slist_free_all(curl_headers);
+    }
+    if (res != CURLE_OK) {
+      LOG(ERROR) << "curl_easy_perform() failed. "
+                 << "Code was \"" << res << "\". "
+                 << "Strerror was \"" << curl_easy_strerror(res) << "\". "
+                 << "Error buffer was \"" << error_buf << "\".";
+      return {false, -1};
+    }
+    long http_code = 0;
+    curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
+    return {true, http_code};
+  }
+
+  CurlResponse<std::string> DownloadToFile(
+      const std::string& url, const std::string& path,
+      const std::vector<std::string>& headers) {
+    LOG(INFO) << "Attempting to save \"" << url << "\" to \"" << path << "\"";
+    std::fstream stream;
+    auto callback = [&stream, path](char* data, size_t size) -> bool {
+      if (data == nullptr) {
+        stream.open(path, std::ios::out | std::ios::binary | std::ios::trunc);
+        return !stream.fail();
+      }
+      stream.write(data, size);
+      return !stream.fail();
+    };
+    auto callback_res = DownloadToCallback(callback, url, headers);
+    if (!callback_res.data) {
+      return {"", callback_res.http_code};
+    }
+    return {path, callback_res.http_code};
+    std::lock_guard<std::mutex> lock(mutex_);
+    if (!curl_) {
+      LOG(ERROR) << "curl was not initialized\n";
+      return {"", -1};
+    }
+  }
+
+  CurlResponse<std::string> DownloadToString(
+      const std::string& url, const std::vector<std::string>& headers) {
+    std::stringstream stream;
+    auto callback = [&stream](char* data, size_t size) -> bool {
+      if (data == nullptr) {
+        stream = std::stringstream();
+        return true;
+      }
+      stream.write(data, size);
+      return true;
+    };
+    auto callback_res = DownloadToCallback(callback, url, headers);
+    if (!callback_res.data) {
+      return {"", callback_res.http_code};
+    }
+    return {stream.str(), callback_res.http_code};
+  }
+
+  CurlResponse<Json::Value> DownloadToJson(
+      const std::string& url, const std::vector<std::string>& headers) {
+    CurlResponse<std::string> response = DownloadToString(url, headers);
+    const std::string& contents = response.data;
+    Json::CharReaderBuilder builder;
+    std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+    Json::Value json;
+    std::string errorMessage;
+    if (!reader->parse(&*contents.begin(), &*contents.end(), &json,
+                       &errorMessage)) {
+      LOG(ERROR) << "Could not parse json: " << errorMessage;
+      json["error"] = "Failed to parse json.";
+      json["response"] = contents;
+    }
+    return {json, response.http_code};
+  }
+
+  CurlResponse<Json::Value> DeleteToJson(
+      const std::string& url,
+      const std::vector<std::string>& headers) override {
+    std::lock_guard<std::mutex> lock(mutex_);
+    LOG(INFO) << "Attempting to download \"" << url << "\"";
+    if (!curl_) {
+      LOG(ERROR) << "curl was not initialized\n";
+      return {"", -1};
+    }
+    curl_slist* curl_headers = build_slist(headers);
+    curl_easy_reset(curl_);
+    curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "DELETE");
+    curl_easy_setopt(curl_, CURLOPT_CAINFO,
+                     "/etc/ssl/certs/ca-certificates.crt");
+    curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers);
+    curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
+    std::stringstream data_to_read;
+    curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, file_write_callback);
+    curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &data_to_read);
+    char error_buf[CURL_ERROR_SIZE];
+    curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf);
+    curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L);
+    CURLcode res = curl_easy_perform(curl_);
+    if (curl_headers) {
+      curl_slist_free_all(curl_headers);
+    }
+    if (res != CURLE_OK) {
+      LOG(ERROR) << "curl_easy_perform() failed. "
+                 << "Code was \"" << res << "\". "
+                 << "Strerror was \"" << curl_easy_strerror(res) << "\". "
+                 << "Error buffer was \"" << error_buf << "\".";
+      return {"", -1};
+    }
+    long http_code = 0;
+    curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
+
+    auto contents = data_to_read.str();
+    Json::CharReaderBuilder builder;
+    std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+    Json::Value json;
+    std::string errorMessage;
+    if (!reader->parse(&*contents.begin(), &*contents.end(), &json,
+                       &errorMessage)) {
+      LOG(ERROR) << "Could not parse json: " << errorMessage;
+      json["error"] = "Failed to parse json.";
+      json["response"] = contents;
+    }
+    return {json, http_code};
+  }
+
+  std::string UrlEscape(const std::string& text) override {
+    char* escaped_str = curl_easy_escape(curl_, text.c_str(), text.size());
+    std::string ret{escaped_str};
+    curl_free(escaped_str);
+    return ret;
+  }
+
+ private:
+  CURL* curl_;
+  std::mutex mutex_;
+};
+
+class CurlServerErrorRetryingWrapper : public CurlWrapper {
+ public:
+  CurlServerErrorRetryingWrapper(CurlWrapper& inner, int retry_attempts,
+                                 std::chrono::milliseconds retry_delay)
+      : inner_curl_(inner),
+        retry_attempts_(retry_attempts),
+        retry_delay_(retry_delay) {}
+
+  CurlResponse<std::string> PostToString(
+      const std::string& url, const std::string& data,
+      const std::vector<std::string>& headers) override {
+    return RetryImpl<std::string>(
+        [&, this]() { return inner_curl_.PostToString(url, data, headers); });
+  }
+
+  CurlResponse<Json::Value> PostToJson(
+      const std::string& url, const Json::Value& data,
+      const std::vector<std::string>& headers) override {
+    return RetryImpl<Json::Value>(
+        [&, this]() { return inner_curl_.PostToJson(url, data, headers); });
+  }
+
+  CurlResponse<Json::Value> PostToJson(
+      const std::string& url, const std::string& data,
+      const std::vector<std::string>& headers) override {
+    return RetryImpl<Json::Value>(
+        [&, this]() { return inner_curl_.PostToJson(url, data, headers); });
+  }
+
+  CurlResponse<std::string> DownloadToFile(
+      const std::string& url, const std::string& path,
+      const std::vector<std::string>& headers) {
+    return RetryImpl<std::string>(
+        [&, this]() { return inner_curl_.DownloadToFile(url, path, headers); });
+  }
+
+  CurlResponse<std::string> DownloadToString(
+      const std::string& url, const std::vector<std::string>& headers) {
+    return RetryImpl<std::string>(
+        [&, this]() { return inner_curl_.DownloadToString(url, headers); });
+  }
+
+  CurlResponse<Json::Value> DownloadToJson(
+      const std::string& url, const std::vector<std::string>& headers) {
+    return RetryImpl<Json::Value>(
+        [&, this]() { return inner_curl_.DownloadToJson(url, headers); });
+  }
+
+  CurlResponse<bool> DownloadToCallback(
+      DataCallback cb, const std::string& url,
+      const std::vector<std::string>& hdrs) override {
+    return RetryImpl<bool>(
+        [&, this]() { return inner_curl_.DownloadToCallback(cb, url, hdrs); });
+  }
+  CurlResponse<Json::Value> DeleteToJson(
+      const std::string& url,
+      const std::vector<std::string>& headers) override {
+    return RetryImpl<Json::Value>(
+        [&, this]() { return inner_curl_.DeleteToJson(url, headers); });
+  }
+
+  std::string UrlEscape(const std::string& text) override {
+    return inner_curl_.UrlEscape(text);
+  }
+
+ private:
+  template <typename T>
+  CurlResponse<T> RetryImpl(std::function<CurlResponse<T>()> attempt_fn) {
+    CurlResponse<T> response;
+    for (int attempt = 0; attempt != retry_attempts_; ++attempt) {
+      if (attempt != 0) {
+        std::this_thread::sleep_for(retry_delay_);
+      }
+      response = attempt_fn();
+      if (!response.HttpServerError()) {
+        return response;
+      }
+    }
+    return response;
+  }
+
+ private:
+  CurlWrapper& inner_curl_;
+  int retry_attempts_;
+  std::chrono::milliseconds retry_delay_;
+};
+
+}  // namespace
+
+/* static */ std::unique_ptr<CurlWrapper> CurlWrapper::Create() {
+  return std::unique_ptr<CurlWrapper>(new CurlWrapperImpl());
+}
+
+/* static */ std::unique_ptr<CurlWrapper> CurlWrapper::WithServerErrorRetry(
+    CurlWrapper& inner, int retry_attempts,
+    std::chrono::milliseconds retry_delay) {
+  return std::unique_ptr<CurlWrapper>(
+      new CurlServerErrorRetryingWrapper(inner, retry_attempts, retry_delay));
+}
+
+CurlWrapper::~CurlWrapper() = default;
+}
diff --git a/host/libs/web/curl_wrapper.h b/host/libs/web/curl_wrapper.h
new file mode 100644
index 0000000..3e897a4
--- /dev/null
+++ b/host/libs/web/curl_wrapper.h
@@ -0,0 +1,74 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <chrono>
+#include <mutex>
+#include <string>
+
+#include <json/json.h>
+
+namespace cuttlefish {
+
+template <typename T>
+struct CurlResponse {
+  bool HttpInfo() { return http_code >= 100 && http_code <= 199; }
+  bool HttpSuccess() { return http_code >= 200 && http_code <= 299; }
+  bool HttpRedirect() { return http_code >= 300 && http_code <= 399; }
+  bool HttpClientError() { return http_code >= 400 && http_code <= 499; }
+  bool HttpServerError() { return http_code >= 500 && http_code <= 599; }
+
+  T data;
+  long http_code;
+};
+
+class CurlWrapper {
+ public:
+  typedef std::function<bool(char*, size_t)> DataCallback;
+
+  static std::unique_ptr<CurlWrapper> Create();
+  static std::unique_ptr<CurlWrapper> WithServerErrorRetry(
+      CurlWrapper&, int retry_attempts, std::chrono::milliseconds retry_delay);
+  virtual ~CurlWrapper();
+
+  virtual CurlResponse<std::string> PostToString(
+      const std::string& url, const std::string& data,
+      const std::vector<std::string>& headers = {}) = 0;
+  virtual CurlResponse<Json::Value> PostToJson(
+      const std::string& url, const std::string& data,
+      const std::vector<std::string>& headers = {}) = 0;
+  virtual CurlResponse<Json::Value> PostToJson(
+      const std::string& url, const Json::Value& data,
+      const std::vector<std::string>& headers = {}) = 0;
+
+  virtual CurlResponse<std::string> DownloadToFile(
+      const std::string& url, const std::string& path,
+      const std::vector<std::string>& headers = {}) = 0;
+  virtual CurlResponse<std::string> DownloadToString(
+      const std::string& url, const std::vector<std::string>& headers = {}) = 0;
+  virtual CurlResponse<Json::Value> DownloadToJson(
+      const std::string& url, const std::vector<std::string>& headers = {}) = 0;
+  virtual CurlResponse<bool> DownloadToCallback(
+      DataCallback callback, const std::string& url,
+      const std::vector<std::string>& headers = {}) = 0;
+
+  virtual CurlResponse<Json::Value> DeleteToJson(
+      const std::string& url, const std::vector<std::string>& headers = {}) = 0;
+
+  virtual std::string UrlEscape(const std::string&) = 0;
+};
+
+}
diff --git a/host/commands/fetcher/install_zip.cc b/host/libs/web/install_zip.cc
similarity index 100%
rename from host/commands/fetcher/install_zip.cc
rename to host/libs/web/install_zip.cc
diff --git a/host/commands/fetcher/install_zip.h b/host/libs/web/install_zip.h
similarity index 100%
rename from host/commands/fetcher/install_zip.h
rename to host/libs/web/install_zip.h
diff --git a/host/libs/websocket/websocket_handler.cpp b/host/libs/websocket/websocket_handler.cpp
index 9d6f636..c80b150 100644
--- a/host/libs/websocket/websocket_handler.cpp
+++ b/host/libs/websocket/websocket_handler.cpp
@@ -18,8 +18,18 @@
 #include <android-base/logging.h>
 #include <libwebsockets.h>
 
+#include "host/libs/websocket/websocket_server.h"
+
 namespace cuttlefish {
 
+namespace {
+void AppendData(const char* data, size_t len, std::string& buffer) {
+  auto ptr = reinterpret_cast<const uint8_t*>(data);
+  buffer.reserve(buffer.size() + len);
+  buffer.insert(buffer.end(), ptr, ptr + len);
+}
+}  // namespace
+
 WebSocketHandler::WebSocketHandler(struct lws* wsi) : wsi_(wsi) {}
 
 void WebSocketHandler::EnqueueMessage(const uint8_t* data, size_t len,
@@ -34,6 +44,8 @@
 // updating the buffer.
 void WebSocketHandler::WriteWsBuffer(WebSocketHandler::WsBuffer& ws_buffer) {
   auto len = ws_buffer.data.size() - LWS_PRE;
+  // For http2 there must be LWS_PRE bytes at the end as well.
+  ws_buffer.data.resize(ws_buffer.data.size() + LWS_PRE);
   auto flags = lws_write_ws_flags(
       ws_buffer.binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT, true, true);
   auto res = lws_write(wsi_, &ws_buffer.data[LWS_PRE], len,
@@ -44,7 +56,7 @@
   if (res < 0) {
     // This shouldn't happen since this function is called in response to a
     // LWS_CALLBACK_SERVER_WRITEABLE call.
-    LOG(FATAL) << "Failed to write data on the websocket";
+    LOG(ERROR) << "Failed to write data on the websocket";
   }
 }
 
@@ -67,4 +79,28 @@
   lws_callback_on_writable(wsi_);
 }
 
+DynHandler::DynHandler(struct lws* wsi) : wsi_(wsi), out_buffer_(LWS_PRE, 0) {}
+
+void DynHandler::AppendDataOut(const std::string& data) {
+  AppendData(data.c_str(), data.size(), out_buffer_);
+}
+
+void DynHandler::AppendDataIn(void* data, size_t len) {
+  AppendData(reinterpret_cast<char*>(data), len, in_buffer_);
+}
+
+int DynHandler::OnWritable() {
+  auto len = out_buffer_.size() - LWS_PRE;
+  // For http2 there must be LWS_PRE bytes at the end as well.
+  out_buffer_.resize(out_buffer_.size() + LWS_PRE);
+  auto res = lws_write(wsi_, reinterpret_cast<uint8_t*>(&out_buffer_[LWS_PRE]),
+                       len, LWS_WRITE_HTTP_FINAL);
+  if (res != len) {
+    // This shouldn't happen since this function is called in response to a
+    // LWS_CALLBACK_SERVER_WRITEABLE call.
+    LOG(ERROR) << "Failed to write HTTP response";
+  }
+  return lws_http_transaction_completed(wsi_);
+}
+size_t DynHandler::content_len() const { return out_buffer_.size() - LWS_PRE; }
 }  // namespace cuttlefish
diff --git a/host/libs/websocket/websocket_handler.h b/host/libs/websocket/websocket_handler.h
index 45b51fa..e9a63ca 100644
--- a/host/libs/websocket/websocket_handler.h
+++ b/host/libs/websocket/websocket_handler.h
@@ -16,6 +16,7 @@
 #pragma once
 
 #include <deque>
+#include <string>
 #include <vector>
 
 struct lws;
@@ -63,4 +64,46 @@
   virtual std::shared_ptr<WebSocketHandler> Build(struct lws* wsi) = 0;
 };
 
+class WebSocketServer;
+
+enum class HttpStatusCode : int {
+  // From https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
+  Ok = 200,
+  NoContent = 204,
+  BadRequest = 400,
+  Unauthorized = 401,
+  NotFound = 404,
+  MethodNotAllowed = 405,
+  Conflict = 409,
+};
+
+class DynHandler {
+ public:
+  DynHandler(struct lws* wsi);
+
+  virtual ~DynHandler() = default;
+  // TODO (jemoreira): Allow more than just JSON replies
+  // TODO (jemoreira): Receive request parameters
+  // Handle a GET request.
+  virtual HttpStatusCode DoGet() = 0;
+  // Handle a POST request.
+  virtual HttpStatusCode DoPost() = 0;
+
+ protected:
+  void AppendDataOut(const std::string& data);
+  const std::string& GetDataIn() const { return in_buffer_; }
+
+ private:
+  friend WebSocketServer;
+  void AppendDataIn(void* data, size_t len);
+  int OnWritable();
+  size_t content_len() const;
+
+  struct lws* wsi_;
+  std::string in_buffer_ = {};
+  std::string out_buffer_ = {};
+};
+
+using DynHandlerFactory =
+    std::function<std::unique_ptr<DynHandler>(struct lws*)>;
 }  // namespace cuttlefish
diff --git a/host/libs/websocket/websocket_server.cpp b/host/libs/websocket/websocket_server.cpp
index 40f1357..f48209b 100644
--- a/host/libs/websocket/websocket_server.cpp
+++ b/host/libs/websocket/websocket_server.cpp
@@ -26,29 +26,158 @@
 #include <host/libs/websocket/websocket_handler.h>
 
 namespace cuttlefish {
-WebSocketServer::WebSocketServer(
-    const char* protocol_name,
-    const std::string &certs_dir,
-    const std::string &assets_dir,
-    int server_port) {
-  std::string cert_file = certs_dir + "/server.crt";
-  std::string key_file = certs_dir + "/server.key";
-  std::string ca_file = certs_dir + "/CA.crt";
+namespace {
+
+std::string GetPath(struct lws* wsi) {
+  auto len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI);
+  std::string path(len + 1, '\0');
+  auto ret = lws_hdr_copy(wsi, path.data(), path.size(), WSI_TOKEN_GET_URI);
+  if (ret <= 0) {
+    len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH);
+    path.resize(len + 1, '\0');
+    ret =
+        lws_hdr_copy(wsi, path.data(), path.size(), WSI_TOKEN_HTTP_COLON_PATH);
+  }
+  if (ret < 0) {
+    LOG(FATAL) << "Something went wrong getting the path";
+  }
+  path.resize(len);
+  return path;
+}
+
+const std::vector<std::pair<std::string, std::string>> kCORSHeaders = {
+    {"Access-Control-Allow-Origin:", "*"},
+    {"Access-Control-Allow-Methods:", "POST, GET, OPTIONS"},
+    {"Access-Control-Allow-Headers:",
+     "Content-Type, Access-Control-Allow-Headers, Authorization, "
+     "X-Requested-With, Accept"}};
+
+bool AddCORSHeaders(struct lws* wsi, unsigned char** buffer_ptr,
+                    unsigned char* buffer_end) {
+  for (const auto& header : kCORSHeaders) {
+    const auto& name = header.first;
+    const auto& value = header.second;
+    if (lws_add_http_header_by_name(
+            wsi, reinterpret_cast<const unsigned char*>(name.c_str()),
+            reinterpret_cast<const unsigned char*>(value.c_str()), value.size(),
+            buffer_ptr, buffer_end)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool WriteCommonHttpHeaders(int status, const char* mime_type,
+                            size_t content_len, struct lws* wsi) {
+  constexpr size_t BUFF_SIZE = 2048;
+  uint8_t header_buffer[LWS_PRE + BUFF_SIZE];
+  const auto start = &header_buffer[LWS_PRE];
+  auto p = &header_buffer[LWS_PRE];
+  auto end = start + BUFF_SIZE;
+  if (lws_add_http_common_headers(wsi, status, mime_type, content_len, &p,
+                                  end)) {
+    LOG(ERROR) << "Failed to write headers for response";
+    return false;
+  }
+  if (!AddCORSHeaders(wsi, &p, end)) {
+    LOG(ERROR) << "Failed to write CORS headers for response";
+    return false;
+  }
+  if (lws_finalize_write_http_header(wsi, start, &p, end)) {
+    LOG(ERROR) << "Failed to finalize headers for response";
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+WebSocketServer::WebSocketServer(const char* protocol_name,
+                                 const std::string& assets_dir, int server_port)
+    : WebSocketServer(protocol_name, "", assets_dir, server_port) {}
+
+WebSocketServer::WebSocketServer(const char* protocol_name,
+                                 const std::string& certs_dir,
+                                 const std::string& assets_dir, int server_port)
+    : protocol_name_(protocol_name),
+      assets_dir_(assets_dir),
+      certs_dir_(certs_dir),
+      server_port_(server_port) {}
+
+void WebSocketServer::InitializeLwsObjects() {
+  std::string cert_file = certs_dir_ + "/server.crt";
+  std::string key_file = certs_dir_ + "/server.key";
+  std::string ca_file = certs_dir_ + "/CA.crt";
 
   retry_ = {
       .secs_since_valid_ping = 3,
       .secs_since_valid_hangup = 10,
   };
 
-  struct lws_protocols protocols[] = {
-      {protocol_name, ServerCallback, 4096, 0, 0, nullptr, 0},
-      {nullptr, nullptr, 0, 0, 0, nullptr, 0}};
+  struct lws_protocols protocols[] =  //
+      {{
+           .name = protocol_name_.c_str(),
+           .callback = WebsocketCallback,
+           .per_session_data_size = 0,
+           .rx_buffer_size = 0,
+           .id = 0,
+           .user = this,
+           .tx_packet_size = 0,
+       },
+       {
+           .name = "__http_polling__",
+           .callback = DynHttpCallback,
+           .per_session_data_size = 0,
+           .rx_buffer_size = 0,
+           .id = 0,
+           .user = this,
+           .tx_packet_size = 0,
+       },
+       {
+           .name = nullptr,
+           .callback = nullptr,
+           .per_session_data_size = 0,
+           .rx_buffer_size = 0,
+           .id = 0,
+           .user = nullptr,
+           .tx_packet_size = 0,
+       }};
 
-  mount_ = {
-      .mount_next = nullptr,
+  dyn_mounts_.reserve(dyn_handler_factories_.size());
+  for (auto& handler_entry : dyn_handler_factories_) {
+    auto& path = handler_entry.first;
+    dyn_mounts_.push_back({
+        .mount_next = nullptr,
+        .mountpoint = path.c_str(),
+        .mountpoint_len = static_cast<uint8_t>(path.size()),
+        .origin = "__http_polling__",
+        .def = nullptr,
+        .protocol = nullptr,
+        .cgienv = nullptr,
+        .extra_mimetypes = nullptr,
+        .interpret = nullptr,
+        .cgi_timeout = 0,
+        .cache_max_age = 0,
+        .auth_mask = 0,
+        .cache_reusable = 0,
+        .cache_revalidate = 0,
+        .cache_intermediaries = 0,
+        .origin_protocol = LWSMPRO_CALLBACK,  // dynamic
+        .basic_auth_login_file = nullptr,
+    });
+  }
+  struct lws_http_mount* next_mount = nullptr;
+  // Set up the linked list after all the mounts have been created to ensure
+  // pointers are not invalidated.
+  for (auto& mount : dyn_mounts_) {
+    mount.mount_next = next_mount;
+    next_mount = &mount;
+  }
+
+  static_mount_ = {
+      .mount_next = next_mount,
       .mountpoint = "/",
       .mountpoint_len = 1,
-      .origin = assets_dir.c_str(),
+      .origin = assets_dir_.c_str(),
       .def = "index.html",
       .protocol = nullptr,
       .cgienv = nullptr,
@@ -71,20 +200,22 @@
               "font-src  https://fonts.gstatic.com/; "};
 
   memset(&info, 0, sizeof info);
-  info.port = server_port;
-  info.mounts = &mount_;
+  info.port = server_port_;
+  info.mounts = &static_mount_;
   info.protocols = protocols;
   info.vhost_name = "localhost";
-  info.ws_ping_pong_interval = 10;
   info.headers = &headers_;
-  info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-  info.ssl_cert_filepath = cert_file.c_str();
-  info.ssl_private_key_filepath = key_file.c_str();
-  if (FileExists(ca_file)) {
-    info.ssl_ca_filepath = ca_file.c_str();
-  }
   info.retry_and_idle_policy = &retry_;
 
+  if (!certs_dir_.empty()) {
+    info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+    info.ssl_cert_filepath = cert_file.c_str();
+    info.ssl_private_key_filepath = key_file.c_str();
+    if (FileExists(ca_file)) {
+      info.ssl_ca_filepath = ca_file.c_str();
+    }
+  }
+
   context_ = lws_create_context(&info);
   if (!context_) {
     LOG(FATAL) << "Failed to create websocket context";
@@ -92,12 +223,19 @@
 }
 
 void WebSocketServer::RegisterHandlerFactory(
-    const std::string &path,
+    const std::string& path,
     std::unique_ptr<WebSocketHandlerFactory> handler_factory_p) {
   handler_factories_[path] = std::move(handler_factory_p);
 }
 
+void WebSocketServer::RegisterDynHandlerFactory(
+    const std::string& path,
+    DynHandlerFactory handler_factory) {
+  dyn_handler_factories_[path] = std::move(handler_factory);
+}
+
 void WebSocketServer::Serve() {
+  InitializeLwsObjects();
   int n = 0;
   while (n >= 0) {
     n = lws_service(context_, 0);
@@ -105,27 +243,127 @@
   lws_context_destroy(context_);
 }
 
-std::unordered_map<struct lws*, std::shared_ptr<WebSocketHandler>> WebSocketServer::handlers_ = {};
-std::unordered_map<std::string, std::unique_ptr<WebSocketHandlerFactory>>
-    WebSocketServer::handler_factories_ = {};
-
-std::string WebSocketServer::GetPath(struct lws* wsi) {
-  auto len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI);
-  std::string path(len + 1, '\0');
-  auto ret = lws_hdr_copy(wsi, path.data(), path.size(), WSI_TOKEN_GET_URI);
-  if (ret <= 0) {
-      len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH);
-      path.resize(len + 1, '\0');
-      ret = lws_hdr_copy(wsi, path.data(), path.size(), WSI_TOKEN_HTTP_COLON_PATH);
+int WebSocketServer::WebsocketCallback(struct lws* wsi,
+                                       enum lws_callback_reasons reason,
+                                       void* user, void* in, size_t len) {
+  auto protocol = lws_get_protocol(wsi);
+  if (!protocol) {
+    // Some callback reasons are always handled by the first protocol, before a
+    // wsi struct is even created.
+    return lws_callback_http_dummy(wsi, reason, user, in, len);
   }
-  if (ret < 0) {
-    LOG(FATAL) << "Something went wrong getting the path";
-  }
-  path.resize(len);
-  return path;
+  return reinterpret_cast<WebSocketServer*>(protocol->user)
+      ->ServerCallback(wsi, reason, user, in, len);
 }
 
-int WebSocketServer::ServerCallback(struct lws* wsi, enum lws_callback_reasons reason,
+int WebSocketServer::DynHttpCallback(struct lws* wsi,
+                                     enum lws_callback_reasons reason,
+                                     void* user, void* in, size_t len) {
+  auto protocol = lws_get_protocol(wsi);
+  if (!protocol) {
+    LOG(ERROR) << "No protocol associated with connection";
+    return 1;
+  }
+  return reinterpret_cast<WebSocketServer*>(protocol->user)
+      ->DynServerCallback(wsi, reason, user, in, len);
+}
+
+int WebSocketServer::DynServerCallback(struct lws* wsi,
+                                       enum lws_callback_reasons reason,
+                                       void* user, void* in, size_t len) {
+  switch (reason) {
+    case LWS_CALLBACK_HTTP: {
+      char* path_raw;
+      int path_len;
+      auto method = lws_http_get_uri_and_method(wsi, &path_raw, &path_len);
+      if (method < 0) {
+        return 1;
+      }
+      std::string path(path_raw, path_len);
+      auto handler = InstantiateDynHandler(path, wsi);
+      if (!handler) {
+        if (!WriteCommonHttpHeaders(static_cast<int>(HttpStatusCode::NotFound),
+                                    "application/json", 0, wsi)) {
+          return 1;
+        }
+        return lws_http_transaction_completed(wsi);
+      }
+      dyn_handlers_[wsi] = std::move(handler);
+      switch (method) {
+        case LWSHUMETH_GET: {
+          auto status = dyn_handlers_[wsi]->DoGet();
+          if (!WriteCommonHttpHeaders(static_cast<int>(status),
+                                      "application/json",
+                                      dyn_handlers_[wsi]->content_len(), wsi)) {
+            return 1;
+          }
+          // Write the response later, when the server is ready
+          lws_callback_on_writable(wsi);
+          break;
+        }
+        case LWSHUMETH_POST:
+          // Do nothing until the body has been read
+          break;
+        case LWSHUMETH_OPTIONS: {
+          // Response for CORS preflight
+          auto status = HttpStatusCode::NoContent;
+          if (!WriteCommonHttpHeaders(static_cast<int>(status), "", 0, wsi)) {
+            return 1;
+          }
+          lws_callback_on_writable(wsi);
+          break;
+        }
+        default:
+          LOG(ERROR) << "Unsupported HTTP method: " << method;
+          return 1;
+      }
+      break;
+    }
+    case LWS_CALLBACK_HTTP_BODY: {
+      auto handler = dyn_handlers_[wsi].get();
+      if (!handler) {
+        LOG(WARNING) << "Received body for unknown wsi";
+        return 1;
+      }
+      handler->AppendDataIn(in, len);
+      break;
+    }
+    case LWS_CALLBACK_HTTP_BODY_COMPLETION: {
+      auto handler = dyn_handlers_[wsi].get();
+      if (!handler) {
+        LOG(WARNING) << "Unexpected body completion event from unknown wsi";
+        return 1;
+      }
+      auto status = handler->DoPost();
+      if (!WriteCommonHttpHeaders(static_cast<int>(status), "application/json",
+                                  dyn_handlers_[wsi]->content_len(), wsi)) {
+        return 1;
+      }
+      lws_callback_on_writable(wsi);
+      break;
+    }
+    case LWS_CALLBACK_HTTP_WRITEABLE: {
+      auto handler = dyn_handlers_[wsi].get();
+      if (!handler) {
+        LOG(WARNING) << "Unknown wsi became writable";
+        return 1;
+      }
+      auto ret = handler->OnWritable();
+      dyn_handlers_.erase(wsi);
+      // Make sure the connection (in HTTP 1) or stream (in HTTP 2) is closed
+      // after the response is written
+      return ret;
+    }
+    case LWS_CALLBACK_CLOSED_HTTP:
+      break;
+    default:
+      return lws_callback_http_dummy(wsi, reason, user, in, len);
+  }
+  return 0;
+}
+
+int WebSocketServer::ServerCallback(struct lws* wsi,
+                                    enum lws_callback_reasons reason,
                                     void* user, void* in, size_t len) {
   switch (reason) {
     case LWS_CALLBACK_ESTABLISHED: {
@@ -170,7 +408,7 @@
         handler->OnReceive(reinterpret_cast<const uint8_t*>(in), len,
                            lws_frame_is_binary(wsi), is_final);
       } else {
-        LOG(WARNING) << "Unkwnown wsi sent data";
+        LOG(WARNING) << "Unknown wsi sent data";
       }
       break;
     }
@@ -187,9 +425,21 @@
     LOG(ERROR) << "Wrong path provided in URI: " << uri_path;
     return nullptr;
   } else {
-    LOG(INFO) << "Creating handler for " << uri_path;
+    LOG(VERBOSE) << "Creating handler for " << uri_path;
     return it->second->Build(wsi);
   }
 }
 
+std::unique_ptr<DynHandler> WebSocketServer::InstantiateDynHandler(
+    const std::string& uri_path, struct lws* wsi) {
+  auto it = dyn_handler_factories_.find(uri_path);
+  if (it == dyn_handler_factories_.end()) {
+    LOG(ERROR) << "Wrong path provided in URI: " << uri_path;
+    return nullptr;
+  } else {
+    LOG(VERBOSE) << "Creating handler for " << uri_path;
+    return it->second(wsi);
+  }
+}
+
 }  // namespace cuttlefish
diff --git a/host/libs/websocket/websocket_server.h b/host/libs/websocket/websocket_server.h
index 0695b3b..768578a 100644
--- a/host/libs/websocket/websocket_server.h
+++ b/host/libs/websocket/websocket_server.h
@@ -18,6 +18,7 @@
 
 #include <string>
 #include <unordered_map>
+#include <vector>
 
 #include <android-base/logging.h>
 #include <libwebsockets.h>
@@ -27,32 +28,62 @@
 namespace cuttlefish {
 class WebSocketServer {
  public:
-  WebSocketServer(
-    const char* protocol_name,
-    const std::string &certs_dir,
-    const std::string &assets_dir,
-    int port);
+  // Uses HTTP and WS
+  WebSocketServer(const char* protocol_name, const std::string& assets_dir,
+                  int port);
+  // Uses HTTPS and WSS when a certificates directory is provided
+  WebSocketServer(const char* protocol_name, const std::string& certs_dir,
+                  const std::string& assets_dir, int port);
   ~WebSocketServer() = default;
 
+  // Register a handler factory for websocket connections. A new handler will be
+  // created for each new websocket connection.
   void RegisterHandlerFactory(
-    const std::string &path,
-    std::unique_ptr<WebSocketHandlerFactory> handler_factory_p);
+      const std::string& path,
+      std::unique_ptr<WebSocketHandlerFactory> handler_factory_p);
+
+  // Register a handler factory for dynamic HTTP requests. A new handler will be
+  // created for each HTTP request.
+  void RegisterDynHandlerFactory(const std::string& path,
+                                 DynHandlerFactory handler_factory);
+
   void Serve();
 
-
  private:
-  static std::unordered_map<struct lws*, std::shared_ptr<WebSocketHandler>> handlers_;
-  static std::unordered_map<std::string, std::unique_ptr<WebSocketHandlerFactory>>
-      handler_factories_;
+  static int WebsocketCallback(struct lws* wsi,
+                               enum lws_callback_reasons reason, void* user,
+                               void* in, size_t len);
 
-  static std::string GetPath(struct lws* wsi);
-  static int ServerCallback(struct lws* wsi, enum lws_callback_reasons reason,
+  static int DynHttpCallback(struct lws* wsi, enum lws_callback_reasons reason,
+                             void* user, void* in, size_t len);
+
+  int ServerCallback(struct lws* wsi, enum lws_callback_reasons reason,
                             void* user, void* in, size_t len);
-  static std::shared_ptr<WebSocketHandler> InstantiateHandler(
+  int DynServerCallback(struct lws* wsi,
+                               enum lws_callback_reasons reason, void* user,
+                               void* in, size_t len);
+  std::shared_ptr<WebSocketHandler> InstantiateHandler(
+      const std::string& uri_path, struct lws* wsi);
+  std::unique_ptr<DynHandler> InstantiateDynHandler(
       const std::string& uri_path, struct lws* wsi);
 
+  void InitializeLwsObjects();
+
+  std::unordered_map<struct lws*, std::shared_ptr<WebSocketHandler>> handlers_ =
+      {};
+  std::unordered_map<std::string, std::unique_ptr<WebSocketHandlerFactory>>
+      handler_factories_ = {};
+  std::unordered_map<struct lws*, std::unique_ptr<DynHandler>> dyn_handlers_ =
+      {};
+  std::unordered_map<std::string, DynHandlerFactory> dyn_handler_factories_ =
+      {};
+  std::string protocol_name_;
+  std::string assets_dir_;
+  std::string certs_dir_;
+  int server_port_;
   struct lws_context* context_;
-  struct lws_http_mount mount_;
+  struct lws_http_mount static_mount_;
+  std::vector<struct lws_http_mount> dyn_mounts_ = {};
   struct lws_protocol_vhost_options headers_;
   lws_retry_bo_t retry_;
 };
diff --git a/host/commands/mk_cdisk/Android.bp b/host/libs/wmediumd_controller/Android.bp
similarity index 77%
copy from host/commands/mk_cdisk/Android.bp
copy to host/libs/wmediumd_controller/Android.bp
index a0cf8ba..1343df2 100644
--- a/host/commands/mk_cdisk/Android.bp
+++ b/host/libs/wmediumd_controller/Android.bp
@@ -17,25 +17,24 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-cc_binary {
-    name: "mk_cdisk",
+cc_library_static {
+    name: "libcuttlefish_wmediumd_controller",
     srcs: [
-        "mk_cdisk.cc",
+        "wmediumd_api_protocol.cpp",
+        "wmediumd_controller.cpp",
+    ],
+    export_include_dirs: [
+        ".",
+    ],
+    header_libs: [
+        "wmediumd_headers",
     ],
     shared_libs: [
+        "libbase",
         "libcuttlefish_fs",
         "libcuttlefish_utils",
-        "libbase",
-        "libjsoncpp",
-        "liblog",
-        "libz",
     ],
     static_libs: [
-        "libcdisk_spec",
-        "libext2_uuid",
-        "libimage_aggregator",
-        "libprotobuf-cpp-lite",
-        "libsparse",
     ],
     defaults: ["cuttlefish_host"],
 }
diff --git a/host/libs/wmediumd_controller/wmediumd_api_protocol.cpp b/host/libs/wmediumd_controller/wmediumd_api_protocol.cpp
new file mode 100644
index 0000000..61ce771
--- /dev/null
+++ b/host/libs/wmediumd_controller/wmediumd_api_protocol.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "wmediumd_api_protocol.h"
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include <cstdlib>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "common/libs/fs/shared_buf.h"
+
+template <class T>
+static void AppendBinaryRepresentation(std::string& buf, const T& data) {
+  std::copy(reinterpret_cast<const char*>(&data),
+            reinterpret_cast<const char*>(&data) + sizeof(T),
+            std::back_inserter(buf));
+}
+
+namespace cuttlefish {
+
+std::string WmediumdMessage::Serialize(void) const {
+  std::string result;
+
+  AppendBinaryRepresentation(result, this->Type());
+
+  std::string body;
+  this->SerializeBody(body);
+
+  AppendBinaryRepresentation(result, static_cast<uint32_t>(body.size()));
+
+  std::copy(std::begin(body), std::end(body), std::back_inserter(result));
+
+  return result;
+}
+
+void WmediumdMessageSetControl::SerializeBody(std::string& buf) const {
+  AppendBinaryRepresentation(buf, flags_);
+}
+
+WmediumdMessageSetSnr::WmediumdMessageSetSnr(const std::string& node1,
+                                             const std::string& node2,
+                                             uint8_t snr) {
+  auto splitted_mac1 = android::base::Split(node1, ":");
+  auto splitted_mac2 = android::base::Split(node2, ":");
+
+  if (splitted_mac1.size() != 6) {
+    LOG(FATAL) << "invalid mac address length " << node1;
+  }
+
+  if (splitted_mac2.size() != 6) {
+    LOG(FATAL) << "invalid mac address length " << node2;
+  }
+
+  for (int i = 0; i < 6; i++) {
+    char* end_ptr;
+    node1_mac_[i] = (uint8_t)strtol(splitted_mac1[i].c_str(), &end_ptr, 16);
+    if (end_ptr != splitted_mac1[i].c_str() + splitted_mac1[i].size()) {
+      LOG(FATAL) << "cannot parse " << splitted_mac1[i] << " of " << node1;
+    }
+
+    node2_mac_[i] = (uint8_t)strtol(splitted_mac2[i].c_str(), &end_ptr, 16);
+    if (end_ptr != splitted_mac2[i].c_str() + splitted_mac2[i].size()) {
+      LOG(FATAL) << "cannot parse " << splitted_mac2[i] << " of " << node1;
+    }
+  }
+
+  snr_ = snr;
+}
+
+void WmediumdMessageSetSnr::SerializeBody(std::string& buf) const {
+  std::copy(std::begin(node1_mac_), std::end(node1_mac_),
+            std::back_inserter(buf));
+  std::copy(std::begin(node2_mac_), std::end(node2_mac_),
+            std::back_inserter(buf));
+  buf.push_back(snr_);
+}
+
+void WmediumdMessageReloadConfig::SerializeBody(std::string& buf) const {
+  std::copy(std::begin(config_path_), std::end(config_path_),
+            std::back_inserter(buf));
+  buf.push_back('\0');
+}
+
+void WmediumdMessageStartPcap::SerializeBody(std::string& buf) const {
+  std::copy(std::begin(pcap_path_), std::end(pcap_path_),
+            std::back_inserter(buf));
+  buf.push_back('\0');
+}
+
+std::optional<WmediumdMessageStationsList> WmediumdMessageStationsList::Parse(
+    const WmediumdMessageReply& reply) {
+  size_t pos = 0;
+  size_t dataSize = reply.Size();
+  auto data = reply.Data();
+
+  if (reply.Type() != WmediumdMessageType::kStationsList) {
+    LOG(FATAL) << "expected reply type "
+               << static_cast<uint32_t>(WmediumdMessageType::kStationsList)
+               << ", got " << static_cast<uint32_t>(reply.Type()) << std::endl;
+  }
+
+  WmediumdMessageStationsList result;
+
+  if (pos + sizeof(uint32_t) > dataSize) {
+    LOG(ERROR) << "invalid response size";
+    return std::nullopt;
+  }
+
+  uint32_t count = *reinterpret_cast<const uint32_t*>(data + pos);
+  pos += sizeof(uint32_t);
+
+  for (uint32_t i = 0; i < count; ++i) {
+    if (pos + sizeof(wmediumd_station_info) > dataSize) {
+      LOG(ERROR) << "invalid response size";
+      return std::nullopt;
+    }
+    result.station_list_.push_back(
+        *reinterpret_cast<const wmediumd_station_info*>(data + pos));
+    pos += sizeof(wmediumd_station_info);
+  }
+
+  return result;
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/wmediumd_controller/wmediumd_api_protocol.h b/host/libs/wmediumd_controller/wmediumd_api_protocol.h
new file mode 100644
index 0000000..b5cedd6
--- /dev/null
+++ b/host/libs/wmediumd_controller/wmediumd_api_protocol.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+
+#include <wmediumd/api.h>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace cuttlefish {
+
+enum class WmediumdMessageType : uint32_t {
+  kInvalid = 0,
+  kAck = 1,
+  kRegister = 2,
+  kUnregister = 3,
+  kNetlink = 4,
+  kSetControl = 5,
+  kTxStart = 6,
+  kGetStations = 7,
+  kSetSnr = 8,
+  kReloadConfig = 9,
+  kReloadCurrentConfig = 10,
+  kStartPcap = 11,
+  kStopPcap = 12,
+  kStationsList = 13,
+};
+
+class WmediumdMessage {
+ public:
+  virtual ~WmediumdMessage() {}
+
+  std::string Serialize(void) const;
+
+  virtual WmediumdMessageType Type() const = 0;
+
+ private:
+  virtual void SerializeBody(std::string&) const {};
+};
+
+class WmediumdMessageSetControl : public WmediumdMessage {
+ public:
+  WmediumdMessageSetControl(uint32_t flags) : flags_(flags) {}
+
+  WmediumdMessageType Type() const override {
+    return WmediumdMessageType::kSetControl;
+  }
+
+ private:
+  void SerializeBody(std::string& out) const override;
+  uint32_t flags_;
+};
+
+class WmediumdMessageSetSnr : public WmediumdMessage {
+ public:
+  WmediumdMessageSetSnr(const std::string& node1, const std::string& node2,
+                        uint8_t snr);
+
+  WmediumdMessageType Type() const override {
+    return WmediumdMessageType::kSetSnr;
+  }
+
+ private:
+  void SerializeBody(std::string& out) const override;
+
+  uint8_t node1_mac_[6];
+  uint8_t node2_mac_[6];
+  uint8_t snr_;
+};
+
+class WmediumdMessageReloadConfig : public WmediumdMessage {
+ public:
+  WmediumdMessageReloadConfig(const std::string& configPath)
+      : config_path_(configPath) {}
+
+  WmediumdMessageType Type() const override {
+    return WmediumdMessageType::kReloadConfig;
+  }
+
+ private:
+  void SerializeBody(std::string& out) const override;
+
+  std::string config_path_;
+};
+
+class WmediumdMessageReloadCurrentConfig : public WmediumdMessage {
+ public:
+  WmediumdMessageReloadCurrentConfig() = default;
+
+  WmediumdMessageType Type() const override {
+    return WmediumdMessageType::kReloadCurrentConfig;
+  }
+};
+
+class WmediumdMessageStartPcap : public WmediumdMessage {
+ public:
+  WmediumdMessageStartPcap(const std::string& pcapPath)
+      : pcap_path_(pcapPath) {}
+
+  WmediumdMessageType Type() const override {
+    return WmediumdMessageType::kStartPcap;
+  }
+
+ private:
+  void SerializeBody(std::string& out) const override;
+
+  std::string pcap_path_;
+};
+
+class WmediumdMessageStopPcap : public WmediumdMessage {
+ public:
+  WmediumdMessageStopPcap() = default;
+
+  WmediumdMessageType Type() const override {
+    return WmediumdMessageType::kStopPcap;
+  }
+};
+
+class WmediumdMessageGetStations : public WmediumdMessage {
+ public:
+  WmediumdMessageGetStations() = default;
+
+  WmediumdMessageType Type() const override {
+    return WmediumdMessageType::kGetStations;
+  }
+};
+
+class WmediumdMessageReply : public WmediumdMessage {
+ public:
+  WmediumdMessageReply() = default;
+  WmediumdMessageReply(WmediumdMessageType type, const std::string& data)
+      : type_(type), data_(data) {}
+
+  WmediumdMessageType Type() const override { return type_; }
+
+  size_t Size() const { return data_.size(); }
+  const char* Data() const { return data_.data(); }
+
+ private:
+  WmediumdMessageType type_;
+  std::string data_;
+};
+
+class WmediumdMessageStationsList : public WmediumdMessage {
+ public:
+  WmediumdMessageStationsList() = default;
+  static std::optional<WmediumdMessageStationsList> Parse(
+      const WmediumdMessageReply& reply);
+
+  WmediumdMessageType Type() const override {
+    return WmediumdMessageType::kStationsList;
+  }
+
+  const std::vector<wmediumd_station_info>& GetStations() const {
+    return station_list_;
+  }
+
+ private:
+  std::vector<wmediumd_station_info> station_list_;
+};
+
+}  // namespace cuttlefish
diff --git a/host/libs/wmediumd_controller/wmediumd_controller.cpp b/host/libs/wmediumd_controller/wmediumd_controller.cpp
new file mode 100644
index 0000000..a1e2d10
--- /dev/null
+++ b/host/libs/wmediumd_controller/wmediumd_controller.cpp
@@ -0,0 +1,138 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "wmediumd_controller.h"
+
+#include <android-base/logging.h>
+#include <sys/socket.h>
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "common/libs/fs/shared_buf.h"
+
+#include "host/libs/wmediumd_controller/wmediumd_api_protocol.h"
+
+namespace cuttlefish {
+
+std::unique_ptr<WmediumdController> WmediumdController::New(
+    const std::string& serverSocketPath) {
+  std::unique_ptr<WmediumdController> result(new WmediumdController);
+
+  if (!result->Connect(serverSocketPath)) {
+    return nullptr;
+  }
+
+  return result;
+}
+
+bool WmediumdController::Connect(const std::string& serverSocketPath) {
+  wmediumd_socket_ =
+      SharedFD::SocketLocalClient(serverSocketPath, false, SOCK_STREAM);
+
+  if (!wmediumd_socket_->IsOpen()) {
+    LOG(ERROR) << "Cannot connect wmediumd control socket " << serverSocketPath
+               << ": " << wmediumd_socket_->StrError();
+    return false;
+  }
+
+  return SetControl(0);
+}
+
+bool WmediumdController::SetSnr(const std::string& node1,
+                                const std::string& node2, uint8_t snr) {
+  return SendMessage(WmediumdMessageSetSnr(node1, node2, snr));
+}
+
+bool WmediumdController::SetControl(const uint32_t flags) {
+  return SendMessage(WmediumdMessageSetControl(flags));
+}
+
+bool WmediumdController::ReloadCurrentConfig(void) {
+  return SendMessage(WmediumdMessageReloadCurrentConfig());
+}
+
+bool WmediumdController::ReloadConfig(const std::string& configPath) {
+  return SendMessage(WmediumdMessageReloadConfig(configPath));
+}
+
+bool WmediumdController::StartPcap(const std::string& pcapPath) {
+  return SendMessage(WmediumdMessageStartPcap(pcapPath));
+}
+
+bool WmediumdController::StopPcap(void) {
+  return SendMessage(WmediumdMessageStopPcap());
+}
+
+std::optional<WmediumdMessageStationsList> WmediumdController::GetStations(
+    void) {
+  auto reply = SendMessageWithReply(WmediumdMessageGetStations());
+
+  if (!reply) {
+    return std::nullopt;
+  }
+
+  return WmediumdMessageStationsList::Parse(*reply);
+}
+
+bool WmediumdController::SendMessage(const WmediumdMessage& message) {
+  auto reply = SendMessageWithReply(message);
+
+  if (!reply) {
+    return false;
+  }
+
+  if (reply->Type() != WmediumdMessageType::kAck) {
+    return false;
+  }
+
+  return true;
+}
+
+std::optional<WmediumdMessageReply> WmediumdController::SendMessageWithReply(
+    const WmediumdMessage& message) {
+  auto sendResult = SendAll(wmediumd_socket_, message.Serialize());
+
+  if (!sendResult) {
+    LOG(ERROR) << "sendmessage failed: " << wmediumd_socket_->StrError();
+    return std::nullopt;
+  }
+
+  std::string recvHeader = RecvAll(wmediumd_socket_, sizeof(uint32_t) * 2);
+
+  if (recvHeader.size() != sizeof(uint32_t) * 2) {
+    LOG(ERROR)
+        << "error: RecvAll failed while receiving result header from server";
+    return std::nullopt;
+  }
+
+  uint32_t type = *reinterpret_cast<const uint32_t*>(recvHeader.c_str());
+  uint32_t dataLen =
+      *reinterpret_cast<const uint32_t*>(recvHeader.c_str() + sizeof(uint32_t));
+
+  std::string recvData = RecvAll(wmediumd_socket_, dataLen);
+
+  if (recvData.size() != dataLen) {
+    LOG(ERROR)
+        << "error: RecvAll failed while receiving result data from server";
+    return std::nullopt;
+  }
+
+  return WmediumdMessageReply(static_cast<WmediumdMessageType>(type), recvData);
+}
+
+}  // namespace cuttlefish
diff --git a/host/libs/wmediumd_controller/wmediumd_controller.h b/host/libs/wmediumd_controller/wmediumd_controller.h
new file mode 100644
index 0000000..10fd577
--- /dev/null
+++ b/host/libs/wmediumd_controller/wmediumd_controller.h
@@ -0,0 +1,59 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/libs/wmediumd_controller/wmediumd_api_protocol.h"
+
+namespace cuttlefish {
+
+class WmediumdController {
+ public:
+  static std::unique_ptr<WmediumdController> New(
+      const std::string& serverSocketPath);
+
+  virtual ~WmediumdController() {}
+
+  WmediumdController(const WmediumdController& rhs) = delete;
+  WmediumdController& operator=(const WmediumdController& rhs) = delete;
+
+  WmediumdController(WmediumdController&& rhs) = delete;
+  WmediumdController& operator=(WmediumdController&& rhs) = delete;
+
+  bool SetControl(const uint32_t flags);
+  bool SetSnr(const std::string& node1, const std::string& node2, uint8_t snr);
+  bool ReloadCurrentConfig(void);
+  bool ReloadConfig(const std::string& configPath);
+  bool StartPcap(const std::string& pcapPath);
+  bool StopPcap(void);
+  std::optional<WmediumdMessageStationsList> GetStations(void);
+
+ private:
+  WmediumdController() {}
+
+  bool Connect(const std::string& serverSocketPath);
+  bool SendMessage(const WmediumdMessage& message);
+  std::optional<WmediumdMessageReply> SendMessageWithReply(
+      const WmediumdMessage& message);
+
+  SharedFD wmediumd_socket_;
+};
+
+}  // namespace cuttlefish
diff --git a/host_package.mk b/host_package.mk
index bd6b51f..e1afedf 100644
--- a/host_package.mk
+++ b/host_package.mk
@@ -1,6 +1,6 @@
-cvd_host_packages := $(SOONG_HOST_OUT)/cvd-host_package.tar.gz
+cvd_host_packages := $(HOST_OUT)/cvd-host_package.tar.gz
 ifeq ($(HOST_CROSS_OS)_$(HOST_CROSS_ARCH),linux_bionic_arm64)
-  cvd_host_packages += $(SOONG_OUT_DIR)/host/$(HOST_CROSS_OS)-$(HOST_CROSS_ARCH)/cvd-host_package.tar.gz
+  cvd_host_packages += $(OUT_DIR)/host/$(HOST_CROSS_OS)-$(HOST_CROSS_ARCH)/cvd-host_package.tar.gz
 endif
 
 .PHONY: hosttar
diff --git a/iwyu.imp b/iwyu.imp
new file mode 100644
index 0000000..0c618ca
--- /dev/null
+++ b/iwyu.imp
@@ -0,0 +1,16 @@
+[
+  { symbol: ["std::basic_istream<>::pos_type", "private", "<fstream>", "public"] },
+  { include: ["\"__hash_table\"", "private", "<unordered_map>", "public"] },
+  { include: ["\"__mutex_base\"", "private", "<mutex>", "public"] },
+  { include: ["\"__string\"", "private", "<string>", "public"] },
+  { include: ["\"json/reader.h\"", "private", "<json/json.h>", "public"] },
+  { include: ["\"json/value.h\"", "private", "<json/json.h>", "public"] },
+  { include: ["\"json/writer.h\"", "private", "<json/json.h>", "public"] },
+  { symbol: ["std::forward", "private", "<utility>", "public" ] },
+  { symbol: ["std::ifstream", "private", "<fstream>", "public" ] },
+  { symbol: ["std::less", "private", "<functional>", "public" ] },
+  { symbol: ["std::move", "private", "<utility>", "public"] },
+  { symbol: ["std::ostream", "private", "<ostream>", "public"] },
+  { symbol: ["std::string", "private", "<string>", "public"] },
+  { symbol: ["std::stringstream", "private", "<sstream>", "public"] },
+]
diff --git a/multiarch-howto.md b/multiarch-howto.md
new file mode 100644
index 0000000..fe24a66
--- /dev/null
+++ b/multiarch-howto.md
@@ -0,0 +1,57 @@
+# Adjusting APT Sources for Multiarch
+
+The Cuttlefish host Debian packages can also be built and used on an `arm64`
+based system. However, because certain parts of it are still `amd64`, the
+APT sources of the system need to be adjusted for multiarch so that package
+dependencies can be correctly looked up and installed.
+
+For detailed context, see [Multiarch HOWTO](https://wiki.debian.org/Multiarch/HOWTO), and this document will use Ubuntu 21.04 (Hirsute) as an example for
+making such adjustments.
+
+The basic idea is to first limit the existing APT sources to `arm64` only,
+so that when a new architecture like `amd64` is added, APT won't try to
+fetch packages for the new architecture from the existing repository, as
+`arm64` packages are in "ports", while `amd64` ones are in the main
+repository. So a line in `/etc/apt/sources.list` such as:
+
+```
+deb http://ports.ubuntu.com/ubuntu-ports hirsute main restricted
+```
+
+would be changed to:
+
+```
+deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports hirsute main restricted
+```
+
+Next, each line of config like the above will be duplicated and modified into
+an entry that corresponds to what's in the main repository, with its
+architecture limited to `amd64`. For example, for the same line as shown above,
+a new entry will be added like this:
+
+```
+deb [arch=amd64] http://archive.ubuntu.com/ubuntu hirsute main restricted
+```
+
+The script below might be handy for this task:
+```bash
+#!/bin/bash
+cp /etc/apt/sources.list ~/sources.list.bak
+(
+  (grep ^deb /etc/apt/sources.list | sed 's/deb /deb [arch=arm64] /') && \
+  (grep ^deb /etc/apt/sources.list | sed 's/deb /deb [arch=amd64] /g; s/ports\.ubuntu/archive.ubuntu/g; s/ubuntu-ports/ubuntu/g') \
+) | tee /tmp/sources.list
+mv /tmp/sources.list /etc/apt/sources.list
+```
+**Note:** please run the above script as `root`, and adjust for differences in
+Ubuntu releases or location prefixed repositories for faster download (e.g.
+`us.archive.ubuntu.com` instead of `archive.ubuntu.com`).
+
+Finally, add the new architecture and do an APT update with:
+```bash
+sudo dpkg --add-architecture amd64
+sudo apt update
+```
+Make sure there's no errors or warnings in the output of `apt update`. To
+restore the previous APT sources list, use the backup file `sources.list.bak`
+saved by the script in your home directory.
diff --git a/recovery/recovery_ui.cpp b/recovery/recovery_ui.cpp
index 217542e..f07e300 100644
--- a/recovery/recovery_ui.cpp
+++ b/recovery/recovery_ui.cpp
@@ -25,5 +25,5 @@
 };
 
 Device* make_device() {
-    return new EthernetDevice(new CuttlefishRecoveryUI);
+    return new EthernetDevice(new CuttlefishRecoveryUI, "eth1");
 }
diff --git a/required_images b/required_images
index 6f5c180..e6f0616 100644
--- a/required_images
+++ b/required_images
@@ -1,4 +1,5 @@
 boot.img
+init_boot.img
 bootloader
 super.img
 userdata.img
diff --git a/shared/BoardConfig.mk b/shared/BoardConfig.mk
index 6be1730..b59b39c 100644
--- a/shared/BoardConfig.mk
+++ b/shared/BoardConfig.mk
@@ -27,7 +27,7 @@
 
 BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE := $(TARGET_RO_FILE_SYSTEM_TYPE)
 
-# Boot partition size: 32M
+# Boot partition size: 64M
 # This is only used for OTA update packages. The image size on disk
 # will not change (as is it not a filesystem.)
 BOARD_BOOTIMAGE_PARTITION_SIZE := 67108864
@@ -36,6 +36,11 @@
 endif
 BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE := 67108864
 
+# init_boot partition size is recommended to be 8MB, it can be larger.
+# When this variable is set, init_boot.img will be built with the generic
+# ramdisk, and that ramdisk will no longer be included in boot.img.
+BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE := 8388608
+
 # Build a separate vendor.img partition
 BOARD_USES_VENDORIMAGE := true
 BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE := $(TARGET_RO_FILE_SYSTEM_TYPE)
@@ -58,30 +63,43 @@
 
 # Build a separate vendor_dlkm partition
 BOARD_USES_VENDOR_DLKMIMAGE := true
-BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE := ext4
+BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE := $(TARGET_RO_FILE_SYSTEM_TYPE)
 TARGET_COPY_OUT_VENDOR_DLKM := vendor_dlkm
 
 # Build a separate odm_dlkm partition
 BOARD_USES_ODM_DLKMIMAGE := true
-BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE := ext4
+BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE := $(TARGET_RO_FILE_SYSTEM_TYPE)
 TARGET_COPY_OUT_ODM_DLKM := odm_dlkm
 
-# FIXME: Remove this once we generate the vbmeta digest correctly
-BOARD_AVB_MAKE_VBMETA_IMAGE_ARGS += --flag 2
+# Build a separate system_dlkm partition
+BOARD_USES_SYSTEM_DLKMIMAGE := true
+BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE := $(TARGET_RO_FILE_SYSTEM_TYPE)
+TARGET_COPY_OUT_SYSTEM_DLKM := system_dlkm
+
+# Enable AVB
+BOARD_AVB_ENABLE := true
+BOARD_AVB_ALGORITHM := SHA256_RSA4096
+BOARD_AVB_KEY_PATH := external/avb/test/data/testkey_rsa4096.pem
 
 # Enable chained vbmeta for system image mixing
 BOARD_AVB_VBMETA_SYSTEM := product system system_ext
-BOARD_AVB_VBMETA_SYSTEM_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
-BOARD_AVB_VBMETA_SYSTEM_ALGORITHM := SHA256_RSA2048
+BOARD_AVB_VBMETA_SYSTEM_KEY_PATH := external/avb/test/data/testkey_rsa4096.pem
+BOARD_AVB_VBMETA_SYSTEM_ALGORITHM := SHA256_RSA4096
 BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
 BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX_LOCATION := 1
 
 # Enable chained vbmeta for boot images
-BOARD_AVB_BOOT_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
-BOARD_AVB_BOOT_ALGORITHM := SHA256_RSA2048
+BOARD_AVB_BOOT_KEY_PATH := external/avb/test/data/testkey_rsa4096.pem
+BOARD_AVB_BOOT_ALGORITHM := SHA256_RSA4096
 BOARD_AVB_BOOT_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
 BOARD_AVB_BOOT_ROLLBACK_INDEX_LOCATION := 2
 
+# Enable chained vbmeta for init_boot images
+BOARD_AVB_INIT_BOOT_KEY_PATH := external/avb/test/data/testkey_rsa4096.pem
+BOARD_AVB_INIT_BOOT_ALGORITHM := SHA256_RSA4096
+BOARD_AVB_INIT_BOOT_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
+BOARD_AVB_INIT_BOOT_ROLLBACK_INDEX_LOCATION := 3
+
 # Using sha256 for dm-verity partitions. b/178983355
 # system, system_other, product.
 TARGET_AVB_SYSTEM_HASHTREE_ALGORITHM ?= sha256
@@ -100,9 +118,10 @@
 BOARD_AVB_VENDOR_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
 BOARD_AVB_ODM_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
 
-# vendor_dlkm and odm_dlkm.
+# vendor_dlkm, odm_dlkm, and system_dlkm.
 BOARD_AVB_VENDOR_DLKM_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
 BOARD_AVB_ODM_DLKM_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
+BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
 
 BOARD_USES_GENERIC_AUDIO := false
 USE_CAMERA_STUB := true
@@ -144,7 +163,14 @@
 USE_OPENGL_RENDERER := true
 
 # Wifi.
+ifeq ($(PRODUCT_ENFORCE_MAC80211_HWSIM),true)
+BOARD_WLAN_DEVICE           := emulator
+BOARD_HOSTAPD_PRIVATE_LIB   := lib_driver_cmd_simulated_cf
+WIFI_HIDL_FEATURE_DUAL_INTERFACE := true
+WIFI_HAL_INTERFACE_COMBINATIONS := {{{STA}, 1}, {{AP}, 1}, {{P2P}, 1}}
+else
 BOARD_WLAN_DEVICE           := wlan0
+endif
 BOARD_HOSTAPD_DRIVER        := NL80211
 BOARD_WPA_SUPPLICANT_DRIVER := NL80211
 BOARD_WPA_SUPPLICANT_PRIVATE_LIB := lib_driver_cmd_simulated_cf
@@ -182,7 +208,7 @@
 
 BOARD_SUPER_PARTITION_SIZE := 7516192768  # 7GiB
 BOARD_SUPER_PARTITION_GROUPS := google_system_dynamic_partitions google_vendor_dynamic_partitions
-BOARD_GOOGLE_SYSTEM_DYNAMIC_PARTITIONS_PARTITION_LIST := product system system_ext
+BOARD_GOOGLE_SYSTEM_DYNAMIC_PARTITIONS_PARTITION_LIST := product system system_ext system_dlkm
 BOARD_GOOGLE_SYSTEM_DYNAMIC_PARTITIONS_SIZE := 5771362304  # 5.375GiB
 BOARD_GOOGLE_VENDOR_DYNAMIC_PARTITIONS_PARTITION_LIST := odm vendor vendor_dlkm odm_dlkm
 # 1404MiB, reserve 4MiB for dynamic partition metadata
@@ -200,15 +226,28 @@
 # To see full logs from init, disable ratelimiting.
 # The default is 5 messages per second amortized, with a burst of up to 10.
 BOARD_KERNEL_CMDLINE += printk.devkmsg=on
+
+# Print audit messages for all security check failures
+BOARD_KERNEL_CMDLINE += audit=1
+
+# Reboot immediately on panic
+BOARD_KERNEL_CMDLINE += panic=-1
+
+# Always enable one legacy serial port, for alternative earlycon, kgdb, and
+# serial console. Doesn't do anything on ARM/ARM64 + QEMU or Gem5.
+BOARD_KERNEL_CMDLINE += 8250.nr_uarts=1
+
+# Cuttlefish doesn't use CMA, so don't reserve RAM for it
+BOARD_KERNEL_CMDLINE += cma=0
+
+# Default firmware load path
 BOARD_KERNEL_CMDLINE += firmware_class.path=/vendor/etc/
 
-BOARD_KERNEL_CMDLINE += init=/init
-BOARD_BOOTCONFIG += androidboot.hardware=cutf_cvm
-
-# TODO(b/179489292): Remove once kfence is enabled everywhere
-BOARD_KERNEL_CMDLINE += kfence.sample_interval=500
-
+# Needed to boot Android
 BOARD_KERNEL_CMDLINE += loop.max_part=7
+BOARD_KERNEL_CMDLINE += init=/init
+
+BOARD_BOOTCONFIG += androidboot.hardware=cutf_cvm
 
 # TODO(b/182417593): Move all of these module options to modules.options
 # TODO(b/176860479): Remove once goldfish and cuttlefish share a wifi implementation
@@ -217,17 +256,17 @@
 BOARD_BOOTCONFIG += \
     kernel.vmw_vsock_virtio_transport_common.virtio_transport_max_vsock_pkt_buf_size=16384
 
-ifeq ($(TARGET_USERDATAIMAGE_FILE_SYSTEM_TYPE),f2fs)
-BOARD_BOOTCONFIG += androidboot.fstab_suffix=f2fs
-endif
-
-ifeq ($(TARGET_USERDATAIMAGE_FILE_SYSTEM_TYPE),ext4)
-BOARD_BOOTCONFIG += androidboot.fstab_suffix=ext4
-endif
+BOARD_BOOTCONFIG += \
+    androidboot.vendor.apex.com.android.wifi.hal=com.google.cf.wifi_hwsim \
+    androidboot.vendor.apex.com.google.emulated.camera.provider.hal=com.google.emulated.camera.provider.hal \
 
 BOARD_INCLUDE_DTB_IN_BOOTIMG := true
+ifndef BOARD_BOOT_HEADER_VERSION
 BOARD_BOOT_HEADER_VERSION := 4
+endif
 BOARD_MKBOOTIMG_ARGS += --header_version $(BOARD_BOOT_HEADER_VERSION)
+BOARD_INIT_BOOT_HEADER_VERSION := 4
+BOARD_MKBOOTIMG_INIT_ARGS += --header_version $(BOARD_INIT_BOOT_HEADER_VERSION)
 PRODUCT_COPY_FILES += \
     device/google/cuttlefish/dtb.img:dtb.img \
     device/google/cuttlefish/required_images:required_images \
@@ -246,16 +285,6 @@
 endif
 BOARD_MOVE_GSI_AVB_KEYS_TO_VENDOR_BOOT := true
 
-# TARGET_KERNEL_USE is defined in kernel.mk, if not defined in the environment variable.
-# Keep in sync with GKI APEX in device.mk
-ifneq (,$(TARGET_KERNEL_USE))
-  ifneq (,$(filter 5.4, $(TARGET_KERNEL_USE)))
-    BOARD_KERNEL_MODULE_INTERFACE_VERSIONS := 5.4-android12-0
-  else
-    BOARD_KERNEL_MODULE_INTERFACE_VERSIONS := $(TARGET_KERNEL_USE)-android12-unstable
-  endif
-endif
-
 BOARD_GENERIC_RAMDISK_KERNEL_MODULES_LOAD := dm-user.ko
 
 BOARD_HAVE_BLUETOOTH := true
diff --git a/shared/auto/TEST_MAPPING b/shared/auto/TEST_MAPPING
new file mode 100644
index 0000000..99fe083
--- /dev/null
+++ b/shared/auto/TEST_MAPPING
@@ -0,0 +1,28 @@
+{
+  "auto-presubmit": [
+    {
+      "name": "AndroidCarApiTest"
+    },
+    {
+      "name": "CarSecurityPermissionTest"
+    },
+    {
+      "name": "CtsCarTestCases"
+    },
+    {
+      "name": "CtsCarBuiltinApiTestCases"
+    },
+    {
+      "name": "CtsCarHostTestCases"
+    },
+    {
+      "name": "CtsCarBuiltinApiHostTestCases"
+    },
+    {
+      "name": "CarServiceTest"
+    },
+    {
+      "name": "CarServiceUnitTest"
+    }
+  ]
+}
diff --git a/shared/auto/audio_policy_configuration.xml b/shared/auto/audio_policy_configuration.xml
index 93d4130..f1ac5ad 100644
--- a/shared/auto/audio_policy_configuration.xml
+++ b/shared/auto/audio_policy_configuration.xml
@@ -24,8 +24,8 @@
 <audioPolicyConfiguration version="1.0" xmlns:xi="http://www.w3.org/2001/XInclude">
     <!-- Global configuration Decalaration -->
     <globalConfiguration speaker_drc_enabled="true"/>
-
-    <module name="primary" halVersion="2.0">
+    <modules>
+      <module name="primary" halVersion="2.0">
         <attachedDevices>
             <item>Speaker</item>
             <item>Built-In Mic</item>
@@ -61,7 +61,11 @@
             <route type="mix" sink="primary input"
                 sources="Built-In Mic"/>
         </routes>
-    </module>
+      </module>
+
+      <!-- Remote Submix Audio HAL -->
+      <xi:include href="r_submix_audio_policy_configuration.xml"/>
+    </modules>
 
     <xi:include href="audio_policy_volumes.xml"/>
     <xi:include href="default_volume_tables.xml"/>
@@ -69,4 +73,4 @@
     <!-- End of Volume section -->
     <!-- End of Modules section -->
 
-</audioPolicyConfiguration>
\ No newline at end of file
+</audioPolicyConfiguration>
diff --git a/shared/auto/device.mk b/shared/auto/device_vendor.mk
similarity index 66%
rename from shared/auto/device.mk
rename to shared/auto/device_vendor.mk
index 3e6604e..277eaa3 100644
--- a/shared/auto/device.mk
+++ b/shared/auto/device_vendor.mk
@@ -14,13 +14,33 @@
 # limitations under the License.
 #
 
-################################################
-# Begin GCE specific configurations
-
 DEVICE_MANIFEST_FILE += device/google/cuttlefish/shared/auto/manifest.xml
+PRODUCT_MANIFEST_FILES += device/google/cuttlefish/shared/config/product_manifest.xml
+SYSTEM_EXT_MANIFEST_FILES += device/google/cuttlefish/shared/config/system_ext_manifest.xml
 
+$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_vendor.mk)
+$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
+$(call inherit-product, packages/services/Car/car_product/build/car.mk)
 $(call inherit-product, device/google/cuttlefish/shared/device.mk)
 
+PRODUCT_VENDOR_PROPERTIES += \
+    keyguard.no_require_sim=true \
+    ro.cdma.home.operator.alpha=Android \
+    ro.cdma.home.operator.numeric=302780 \
+    ro.com.android.dataroaming=true \
+    ro.telephony.default_network=9 \
+
+# Cuttlefish RIL support
+TARGET_USES_CF_RILD ?= true
+ifeq ($(TARGET_USES_CF_RILD),true)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system_ext.mk)
+PRODUCT_PACKAGES += \
+    libcuttlefish-ril-2 \
+    libcuttlefish-rild
+else
+TARGET_NO_TELEPHONY := true
+endif
+
 # Extend cuttlefish common sepolicy with auto-specific functionality
 BOARD_SEPOLICY_DIRS += device/google/cuttlefish/shared/auto/sepolicy/vendor
 
@@ -28,8 +48,8 @@
 # Begin general Android Auto Embedded configurations
 
 PRODUCT_COPY_FILES += \
-    packages/services/Car/car_product/init/init.bootstat.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw//init.bootstat.rc \
-    packages/services/Car/car_product/init/init.car.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw//init.car.rc
+    packages/services/Car/car_product/init/init.bootstat.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/init.bootstat.rc \
+    packages/services/Car/car_product/init/init.car.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/init.car.rc
 
 ifneq ($(LOCAL_SENSOR_FILE_OVERRIDES),true)
     PRODUCT_COPY_FILES += \
@@ -38,32 +58,32 @@
 endif
 
 PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/car_core_hardware.xml:system/etc/permissions/car_core_hardware.xml \
     frameworks/native/data/etc/android.hardware.bluetooth.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.bluetooth.xml \
     frameworks/native/data/etc/android.hardware.broadcastradio.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.broadcastradio.xml \
     frameworks/native/data/etc/android.hardware.faketouch.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.faketouch.xml \
     frameworks/native/data/etc/android.hardware.screen.landscape.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.screen.landscape.xml \
     frameworks/native/data/etc/android.software.activities_on_secondary_displays.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.activities_on_secondary_displays.xml \
-    frameworks/native/data/etc/car_core_hardware.xml:system/etc/permissions/car_core_hardware.xml \
 
 # Preinstalled packages per user type
 PRODUCT_COPY_FILES += \
     device/google/cuttlefish/shared/auto/preinstalled-packages-product-car-cuttlefish.xml:$(TARGET_COPY_OUT_PRODUCT)/etc/sysconfig/preinstalled-packages-product-car-cuttlefish.xml
 
-LOCAL_AUDIO_PRODUCT_COPY_FILES ?= \
+ifndef LOCAL_AUDIO_PRODUCT_COPY_FILES
+LOCAL_AUDIO_PRODUCT_COPY_FILES := \
     device/google/cuttlefish/shared/auto/car_audio_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/car_audio_configuration.xml \
     device/google/cuttlefish/shared/auto/audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_configuration.xml \
     frameworks/av/services/audiopolicy/config/a2dp_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/a2dp_audio_policy_configuration.xml \
-    frameworks/av/services/audiopolicy/config/usb_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/usb_audio_policy_configuration.xml \
+    frameworks/av/services/audiopolicy/config/usb_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/usb_audio_policy_configuration.xml
+endif
 
-PRODUCT_VENDOR_PROPERTIES += \
-    keyguard.no_require_sim=true \
-    ro.cdma.home.operator.alpha=Android \
-    ro.cdma.home.operator.numeric=302780 \
-    ro.com.android.dataroaming=true \
+# Include display settings for an auto device.
+PRODUCT_COPY_FILES += \
+    device/google/cuttlefish/shared/auto/display_settings.xml:$(TARGET_COPY_OUT_VENDOR)/etc/display_settings.xml
 
 # vehicle HAL
 ifeq ($(LOCAL_VHAL_PRODUCT_PACKAGE),)
-    LOCAL_VHAL_PRODUCT_PACKAGE := android.hardware.automotive.vehicle@2.0-service
+    LOCAL_VHAL_PRODUCT_PACKAGE := android.hardware.automotive.vehicle@V1-emulator-service
     BOARD_SEPOLICY_DIRS += device/google/cuttlefish/shared/auto/sepolicy/vhal
 endif
 PRODUCT_PACKAGES += $(LOCAL_VHAL_PRODUCT_PACKAGE)
@@ -74,6 +94,7 @@
 # AudioControl HAL
 ifeq ($(LOCAL_AUDIOCONTROL_HAL_PRODUCT_PACKAGE),)
     LOCAL_AUDIOCONTROL_HAL_PRODUCT_PACKAGE := android.hardware.automotive.audiocontrol-service.example
+    BOARD_SEPOLICY_DIRS += device/google/cuttlefish/shared/auto/sepolicy/audio
 endif
 PRODUCT_PACKAGES += $(LOCAL_AUDIOCONTROL_HAL_PRODUCT_PACKAGE)
 
@@ -83,31 +104,39 @@
     canhaldump \
     canhalsend
 
-# Cuttlefish RIL support
-TARGET_USES_CF_RILD ?= false
-ifeq ($(TARGET_USES_CF_RILD),true)
-    PRODUCT_PACKAGES += \
-        libcuttlefish-ril-2 \
-        libcuttlefish-rild
+# EVS
+# By default, we enable EvsManager, a sample EVS app, and a mock EVS HAL implementation.
+# If you want to use your own EVS HAL implementation, please set ENABLE_MOCK_EVSHAL as false
+# and add your HAL implementation to the product.  Please also check init.evs.rc and see how
+# you can configure EvsManager to use your EVS HAL implementation.  Similarly, please set
+# ENABLE_SAMPLE_EVS_APP as false if you want to use your own EVS app configuration or own EVS
+# app implementation.
+ENABLE_EVS_SERVICE ?= true
+ENABLE_MOCK_EVSHAL ?= true
+ENABLE_CAREVSSERVICE_SAMPLE ?= true
+ENABLE_SAMPLE_EVS_APP ?= true
+
+ifeq ($(ENABLE_MOCK_EVSHAL), true)
+CUSTOMIZE_EVS_SERVICE_PARAMETER := true
+PRODUCT_PACKAGES += android.hardware.automotive.evs@1.1-service \
+    android.frameworks.automotive.display@1.0-service
+PRODUCT_COPY_FILES += \
+    device/google/cuttlefish/shared/auto/evs/init.evs.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/init.evs.rc
+BOARD_SEPOLICY_DIRS += device/google/cuttlefish/shared/auto/sepolicy/evs
 endif
 
-# system_other support
-PRODUCT_PACKAGES += \
-    cppreopts.sh \
+ifeq ($(ENABLE_SAMPLE_EVS_APP), true)
+PRODUCT_PACKAGES += evs_app
+PRODUCT_COPY_FILES += \
+    device/google/cuttlefish/shared/auto/evs/evs_app_config.json:$(TARGET_COPY_OUT_SYSTEM)/etc/automotive/evs/config_override.json
+BOARD_SEPOLICY_DIRS += packages/services/Car/cpp/evs/apps/sepolicy/private
+endif
 
 BOARD_IS_AUTOMOTIVE := true
 
-$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_base.mk)
-$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
-$(call inherit-product, packages/services/Car/car_product/build/car.mk)
-
-# Placed here due to b/110784510
-PRODUCT_BRAND := generic
-
 DEVICE_PACKAGE_OVERLAYS += device/google/cuttlefish/shared/auto/overlay
 
+PRODUCT_PACKAGES += CarServiceOverlayCuttleFish
+GOOGLE_CAR_SERVICE_OVERLAY += CarServiceOverlayCuttleFishGoogle
+
 TARGET_BOARD_INFO_FILE ?= device/google/cuttlefish/shared/auto/android-info.txt
-
-PRODUCT_ENFORCE_RRO_TARGETS := framework-res
-
-TARGET_NO_TELEPHONY := true
diff --git a/shared/auto/display_settings.xml b/shared/auto/display_settings.xml
new file mode 100644
index 0000000..5065838
--- /dev/null
+++ b/shared/auto/display_settings.xml
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<display-settings>
+    <!-- Use physical port number instead of local id -->
+    <config identifier="1" />
+
+    <!-- Display settings for cluster -->
+    <display name="port:1" dontMoveToTop="true" />
+</display-settings>
diff --git a/shared/auto/evs/evs_app_config.json b/shared/auto/evs/evs_app_config.json
new file mode 100644
index 0000000..a2be62d
--- /dev/null
+++ b/shared/auto/evs/evs_app_config.json
@@ -0,0 +1,43 @@
+
+{
+  "car" : {
+    "width"  : 76.7,
+    "wheelBase" : 117.9,
+    "frontExtent" : 44.7,
+    "rearExtent" : 40
+  },
+  "displays" : [
+    {
+      "_comment": "Display0",
+      "displayPort" : 0,
+      "frontRange" : 100,
+      "rearRange" : 100
+    },
+    {
+      "_comment": "Display1",
+      "displayPort" : 1,
+      "frontRange" : 100,
+      "rearRange" : 100
+    }
+  ],
+  "graphic" : {
+    "frontPixel" : -20,
+    "rearPixel" : 260
+  },
+  "cameras" : [
+    {
+      "cameraId" : "/dev/video10",
+      "function" : "reverse",
+      "x" : 0.0,
+      "y" : 20.0,
+      "z" : 48,
+      "yaw" : 180,
+      "pitch" : -10,
+      "roll" : 0,
+      "hfov" : 115,
+      "vfov" : 80,
+      "hflip" : true,
+      "vflip" : false
+    }
+  ]
+}
diff --git a/shared/auto/evs/init.evs.rc b/shared/auto/evs/init.evs.rc
new file mode 100644
index 0000000..f7dace5
--- /dev/null
+++ b/shared/auto/evs/init.evs.rc
@@ -0,0 +1,11 @@
+on late-init
+    start automotive_display
+    start vendor.evs-hal-mock
+    start evs_manager_cf
+
+service evs_manager_cf /system/bin/evsmanagerd --target hw/0
+    class hal
+    priority -20
+    user automotive_evs
+    group automotive_evs system
+    disabled # will not automatically start with its class; must be explicitly started.
diff --git a/shared/auto/overlay/frameworks/base/core/res/res/values/config.xml b/shared/auto/overlay/frameworks/base/core/res/res/values/config.xml
index e42a10f..bf07d05 100644
--- a/shared/auto/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/shared/auto/overlay/frameworks/base/core/res/res/values/config.xml
@@ -22,20 +22,16 @@
   See also packages/services/Car/service/res/values/config.xml
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <!-- Enable multi-user. -->
-  <bool name="config_enableMultiUserUI" translatable="false">true</bool>
-  <!-- If true, all guest users created on the device will be ephemeral. -->
-  <bool name="config_guestUserEphemeral" translatable="false">true</bool>
-  <!--  Maximum number of users allowed on the device. -->
-  <integer name="config_multiuserMaximumUsers" translatable="false">4</integer>
-  <!-- Restricting eth1 -->
-  <string-array translatable="false" name="config_ethernet_interfaces">
-    <item>eth1;11,12,14;;</item>
-  </string-array>
-  <!-- Car uses hardware amplifier for volume. -->
-  <bool name="config_useFixedVolume">true</bool>
-      <!--
-          Handle volume keys directly in CarAudioService without passing them to the foreground app
-      -->
-  <bool name="config_handleVolumeKeysInWindowManager">true</bool>
+    <!-- Enable multi-user. -->
+    <bool name="config_enableMultiUserUI" translatable="false">true</bool>
+    <!-- If true, all guest users created on the device will be ephemeral. -->
+    <bool name="config_guestUserEphemeral" translatable="false">true</bool>
+    <!--  Maximum number of users allowed on the device. -->
+    <integer name="config_multiuserMaximumUsers" translatable="false">4</integer>
+    <!-- Car uses hardware amplifier for volume. -->
+    <bool name="config_useFixedVolume">true</bool>
+    <!--
+      Handle volume keys directly in CarAudioService without passing them to the foreground app
+    -->
+    <bool name="config_handleVolumeKeysInWindowManager">true</bool>
 </resources>
diff --git a/shared/auto/preinstalled-packages-product-car-cuttlefish.xml b/shared/auto/preinstalled-packages-product-car-cuttlefish.xml
index a156ae8..9f22200 100644
--- a/shared/auto/preinstalled-packages-product-car-cuttlefish.xml
+++ b/shared/auto/preinstalled-packages-product-car-cuttlefish.xml
@@ -26,6 +26,10 @@
         <install-in user-type="FULL" />
         <install-in user-type="SYSTEM" />
     </install-in-user-type>
+    <install-in-user-type package="com.android.localtransport">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
     <install-in-user-type package="com.android.car.hvac">
         <install-in user-type="FULL" />
         <install-in user-type="SYSTEM" />
@@ -95,6 +99,22 @@
 	<install-in user-type="FULL" />
         <install-in user-type="SYSTEM" />
     </install-in-user-type>
+    <install-in-user-type package="com.android.apps.tag">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.imsserviceentitlement">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.cellbroadcastservice">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.cellbroadcastreceiver.module">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
 
 
 <!--
@@ -119,9 +139,6 @@
     <install-in-user-type package="com.android.dynsystem">
         <install-in user-type="FULL" />
     </install-in-user-type>
-    <install-in-user-type package="com.android.localtransport">
-        <install-in user-type="FULL" />
-    </install-in-user-type>
     <install-in-user-type package="com.android.mms.service">
         <install-in user-type="FULL" />
     </install-in-user-type>
diff --git a/shared/auto/rro_overlay/CarServiceOverlay/Android.bp b/shared/auto/rro_overlay/CarServiceOverlay/Android.bp
new file mode 100644
index 0000000..305e983
--- /dev/null
+++ b/shared/auto/rro_overlay/CarServiceOverlay/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+    name: "CarServiceOverlayCuttleFish",
+    resource_dirs: ["res"],
+    manifest: "AndroidManifest.xml",
+    sdk_version: "current",
+    product_specific: true
+}
+
+override_runtime_resource_overlay {
+    name: "CarServiceOverlayCuttleFishGoogle",
+    base: "CarServiceOverlayCuttleFish",
+    package_name: "com.google.android.car.resources.cuttlefish",
+    target_package_name: "com.google.android.car.updatable",
+}
+
diff --git a/shared/auto/rro_overlay/CarServiceOverlay/AndroidManifest.xml b/shared/auto/rro_overlay/CarServiceOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..e3fc998
--- /dev/null
+++ b/shared/auto/rro_overlay/CarServiceOverlay/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.resources.cuttlefish">
+    <application android:hasCode="false"/>
+    <overlay android:priority="5001"
+             android:targetPackage="com.android.car.updatable"
+             android:targetName="CarServiceCustomization"
+             android:resourcesMap="@xml/overlays"
+             android:isStatic="true" />
+</manifest>
diff --git a/shared/auto/overlay/packages/services/Car/service/res/values/config.xml b/shared/auto/rro_overlay/CarServiceOverlay/res/values/config.xml
similarity index 100%
rename from shared/auto/overlay/packages/services/Car/service/res/values/config.xml
rename to shared/auto/rro_overlay/CarServiceOverlay/res/values/config.xml
diff --git a/shared/auto/rro_overlay/CarServiceOverlay/res/xml/overlays.xml b/shared/auto/rro_overlay/CarServiceOverlay/res/xml/overlays.xml
new file mode 100644
index 0000000..f3c730f
--- /dev/null
+++ b/shared/auto/rro_overlay/CarServiceOverlay/res/xml/overlays.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<overlay>
+    <item target="bool/audioUseDynamicRouting" value="@bool/audioUseDynamicRouting" />
+    <item target="bool/audioUseCarVolumeGroupMuting" value="@bool/audioUseCarVolumeGroupMuting" />
+    <item target="bool/audioUseHalDuckingSignals" value="@bool/audioUseHalDuckingSignals" />
+    <item target="array/config_occupant_zones" value="@array/config_occupant_zones" />
+    <item target="array/config_occupant_display_mapping" value="@array/config_occupant_display_mapping" />
+    <item target="string/config_evsRearviewCameraId" value="@string/config_evsRearviewCameraId" />
+    <item target="string/config_evsCameraActivity" value="@string/config_evsCameraActivity" />
+</overlay>
diff --git a/shared/auto/sepolicy/audio/hal_audiocontrol_default.te b/shared/auto/sepolicy/audio/hal_audiocontrol_default.te
new file mode 100644
index 0000000..3ec4f5f
--- /dev/null
+++ b/shared/auto/sepolicy/audio/hal_audiocontrol_default.te
@@ -0,0 +1,2 @@
+# Enable audiocontrol to listen to power policy daemon.
+carpowerpolicy_callback_domain(hal_audiocontrol_default)
diff --git a/shared/auto/sepolicy/evs/automotive_display_service.te b/shared/auto/sepolicy/evs/automotive_display_service.te
new file mode 100644
index 0000000..d3d9ec0
--- /dev/null
+++ b/shared/auto/sepolicy/evs/automotive_display_service.te
@@ -0,0 +1,2 @@
+# Allow automotive_display_service to perform binder IPC to hal_evs_default
+binder_call(automotive_display_service_server, hal_evs_default)
diff --git a/shared/auto/sepolicy/evs/hal_evs_default.te b/shared/auto/sepolicy/evs/hal_evs_default.te
new file mode 100644
index 0000000..88a2934
--- /dev/null
+++ b/shared/auto/sepolicy/evs/hal_evs_default.te
@@ -0,0 +1,16 @@
+# Allow use of USB devices, gralloc buffers, and surface flinger
+hal_client_domain(hal_evs_default, hal_graphics_allocator);
+hal_client_domain(hal_evs_default, hal_graphics_composer)
+
+# Allow the driver to access EGL
+allow hal_evs_default gpu_device:chr_file rw_file_perms;
+allow hal_evs_default gpu_device:dir search;
+
+# Allow the driver to use SurfaceFlinger
+binder_call(hal_evs_default, surfaceflinger);
+allow hal_evs_default surfaceflinger_service:service_manager find;
+allow hal_evs_default ion_device:chr_file r_file_perms;
+
+# Allow the driver to use automotive display proxy service
+binder_call(hal_evs_default, automotive_display_service_server);
+allow hal_evs_default fwk_automotive_display_hwservice:hwservice_manager find;
diff --git a/shared/auto/sepolicy/evs/surfaceflinger.te b/shared/auto/sepolicy/evs/surfaceflinger.te
new file mode 100644
index 0000000..f23d190
--- /dev/null
+++ b/shared/auto/sepolicy/evs/surfaceflinger.te
@@ -0,0 +1,5 @@
+# Allow surfaceflinger to perform binder IPC to hal_evs_default
+binder_call(surfaceflinger, hal_evs_default)
+
+# Allow surfaceflinger to perform binder IPC to automotive_display_service
+binder_call(surfaceflinger, automotive_display_service_server)
diff --git a/shared/config/Android.bp b/shared/config/Android.bp
index 03bbc08..f2d94e4 100644
--- a/shared/config/Android.bp
+++ b/shared/config/Android.bp
@@ -2,6 +2,19 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+filegroup {
+    name: "device_google_cuttlefish_shared_config_pci_ids",
+    srcs: ["pci.ids"],
+    licenses: ["device_google_cuttlefish_shared_config_pci_ids_license"],
+}
+
+license {
+    name: "device_google_cuttlefish_shared_config_pci_ids_license",
+    package_name: "PCI IDS",
+    license_kinds: ["SPDX-license-identifier-BSD-3-Clause"],
+    license_text: ["LICENSE_BSD"],
+}
+
 prebuilt_etc_host {
     name: "cvd_config_auto.json",
     src: "config_auto.json",
@@ -15,12 +28,24 @@
 }
 
 prebuilt_etc_host {
+    name: "cvd_config_go.json",
+    src: "config_go.json",
+    sub_dir: "cvd_config",
+}
+
+prebuilt_etc_host {
     name: "cvd_config_phone.json",
     src: "config_phone.json",
     sub_dir: "cvd_config",
 }
 
 prebuilt_etc_host {
+    name: "cvd_config_slim.json",
+    src: "config_slim.json",
+    sub_dir: "cvd_config",
+}
+
+prebuilt_etc_host {
     name: "cvd_config_tablet.json",
     src: "config_tablet.json",
     sub_dir: "cvd_config",
@@ -31,3 +56,36 @@
     src: "config_tv.json",
     sub_dir: "cvd_config",
 }
+
+prebuilt_etc_host {
+    name: "cvd_config_wear.json",
+    src: "config_wear.json",
+    sub_dir: "cvd_config",
+}
+
+prebuilt_etc_host {
+    name: "grub.cfg",
+    src: "grub.cfg",
+    sub_dir: "grub",
+}
+
+prebuilt_etc {
+    name: "wpa_supplicant_overlay.conf.cf",
+    src: "wpa_supplicant_overlay.conf",
+    filename_from_src: true,
+    relative_install_path: "wifi",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "p2p_supplicant.conf.cf",
+    src: "p2p_supplicant.conf",
+    filename_from_src: true,
+    relative_install_path: "wifi",
+    installable: false,
+}
+
+filegroup {
+    name: "manifest_android.hardware.bluetooth@1.1-service.xml",
+    srcs: ["manifest_android.hardware.bluetooth@1.1-service.xml"]
+}
diff --git a/shared/config/CleanSpec.mk b/shared/config/CleanSpec.mk
index 9556fc5..a388176 100644
--- a/shared/config/CleanSpec.mk
+++ b/shared/config/CleanSpec.mk
@@ -51,3 +51,4 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/android.hardware.graphics.allocator@4.0-service.minigbm.rc)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/bin/hw/android.hardware.graphics.allocator@4.0-service.minigbm)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/bin/hw/android.hardware.gnss@2.1-service)
+$(call add-clean-step, find $(PRODUCT_OUT)/vendor/bin/hw/ -type f -name "android.hardware.drm*" -print0 | xargs -0 rm -f)
diff --git a/shared/config/LICENSE_BSD b/shared/config/LICENSE_BSD
new file mode 100644
index 0000000..540b631
--- /dev/null
+++ b/shared/config/LICENSE_BSD
@@ -0,0 +1,24 @@
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/shared/config/audio_policy.conf b/shared/config/audio_policy.conf
deleted file mode 100644
index 2cbf118..0000000
--- a/shared/config/audio_policy.conf
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Audio policy configuration for generic device builds (cuttlefish audio HAL)
-#
-
-# Global configuration section: lists input and output devices always present on the device
-# as well as the output device selected by default.
-# Devices are designated by a string that corresponds to the enum in audio.h
-
-global_configuration {
-  attached_output_devices AUDIO_DEVICE_OUT_SPEAKER
-  default_output_device AUDIO_DEVICE_OUT_SPEAKER
-  attached_input_devices AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_REMOTE_SUBMIX
-}
-
-# audio hardware module section: contains descriptors for all audio hw modules present on the
-# device. Each hw module node is named after the corresponding hw module library base name.
-# For instance, "primary" corresponds to audio.primary.<device>.so.
-# The "primary" module is mandatory and must include at least one output with
-# AUDIO_OUTPUT_FLAG_PRIMARY flag.
-# Each module descriptor contains one or more output profile descriptors and zero or more
-# input profile descriptors. Each profile lists all the parameters supported by a given output
-# or input stream category.
-# The "channel_masks", "formats", "devices" and "flags" are specified using strings corresponding
-# to enums in audio.h and audio_policy.h. They are concatenated by use of "|" without space or "\n".
-
-audio_hw_modules {
-  primary {
-    outputs {
-      primary {
-        sampling_rates 8000|11025|16000|22050|24000|44100|48000
-        channel_masks AUDIO_CHANNEL_OUT_MONO|AUDIO_CHANNEL_OUT_STEREO
-        formats AUDIO_FORMAT_PCM_16_BIT
-        devices AUDIO_DEVICE_OUT_SPEAKER|AUDIO_DEVICE_OUT_WIRED_HEADPHONE|AUDIO_DEVICE_OUT_WIRED_HEADSET
-        flags AUDIO_OUTPUT_FLAG_PRIMARY
-      }
-    }
-    inputs {
-      primary {
-        sampling_rates 8000|11025|16000|22050|44100|48000
-        channel_masks AUDIO_CHANNEL_IN_MONO|AUDIO_CHANNEL_IN_STEREO
-        formats AUDIO_FORMAT_PCM_16_BIT
-        devices AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_WIRED_HEADSET
-      }
-    }
-  }
-  r_submix {
-    outputs {
-      submix {
-        sampling_rates 48000
-        channel_masks AUDIO_CHANNEL_OUT_STEREO
-        formats AUDIO_FORMAT_PCM_16_BIT
-        devices AUDIO_DEVICE_OUT_REMOTE_SUBMIX
-      }
-    }
-    inputs {
-      submix {
-        sampling_rates 48000
-        channel_masks AUDIO_CHANNEL_IN_STEREO
-        formats AUDIO_FORMAT_PCM_16_BIT
-        devices AUDIO_DEVICE_IN_REMOTE_SUBMIX
-      }
-    }
-  }
-}
diff --git a/shared/config/config_auto.json b/shared/config/config_auto.json
index b8f2bfe..67dd3bc 100644
--- a/shared/config/config_auto.json
+++ b/shared/config/config_auto.json
@@ -1,6 +1,5 @@
 {
-	"x_res" : 1280,
-	"y_res" : 800,
-	"dpi" : 160,
+	"display0": "width=1080,height=600,dpi=120",
+	"display1": "width=400,height=600,dpi=120",
 	"memory_mb" : 4096
 }
diff --git a/shared/config/config_go.json b/shared/config/config_go.json
new file mode 100644
index 0000000..69ad977
--- /dev/null
+++ b/shared/config/config_go.json
@@ -0,0 +1,6 @@
+{
+	"x_res" : 720,
+	"y_res" : 1280,
+	"dpi" : 320,
+	"memory_mb" : 2048
+}
diff --git a/shared/config/config_slim.json b/shared/config/config_slim.json
new file mode 100644
index 0000000..017057f
--- /dev/null
+++ b/shared/config/config_slim.json
@@ -0,0 +1,7 @@
+{
+	"x_res" : 720,
+	"y_res" : 1280,
+	"dpi" : 320,
+	"memory_mb" : 2048,
+	"use_sdcard" : false
+}
diff --git a/shared/config/config_wear.json b/shared/config/config_wear.json
new file mode 100644
index 0000000..59e76ad
--- /dev/null
+++ b/shared/config/config_wear.json
@@ -0,0 +1,7 @@
+{
+	"x_res" : 450,
+	"y_res" : 450,
+	"dpi" : 320,
+	"memory_mb" : 1536,
+	"use_sdcard" : false
+}
diff --git a/shared/config/fstab-erofs.ext4 b/shared/config/fstab-erofs.ext4
deleted file mode 100644
index ec7b3fe..0000000
--- a/shared/config/fstab-erofs.ext4
+++ /dev/null
@@ -1,18 +0,0 @@
-/dev/block/by-name/boot /boot emmc defaults recoveryonly,slotselect
-/dev/block/by-name/vendor_boot /vendor_boot emmc defaults recoveryonly,slotselect
-system /system erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
-# Add all non-dynamic partitions except system, after this comment
-/dev/block/by-name/userdata /data ext4 nodev,noatime,nosuid,errors=panic latemount,wait,check,quota,formattable,fileencryption=aes-256-xts:aes-256-cts,keydirectory=/metadata/vold/metadata_encryption,checkpoint=block
-/dev/block/by-name/metadata /metadata ext4 nodev,noatime,nosuid,errors=panic wait,formattable,first_stage_mount,check
-/dev/block/by-name/misc /misc emmc defaults defaults
-# Add all dynamic partitions except system, after this comment
-odm /odm erofs ro wait,logical,first_stage_mount,slotselect,avb
-product /product erofs ro wait,logical,first_stage_mount,slotselect,avb
-system_ext /system_ext erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
-vendor /vendor erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta
-vendor_dlkm /vendor_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
-odm_dlkm /odm_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
-/dev/block/zram0 none swap defaults zramsize=75%
-/dev/block/vdc1 /sdcard vfat defaults recoveryonly
-/devices/*/block/vdc auto auto defaults voldmanaged=sdcard1:auto,encryptable=userdata
-shared /mnt/vendor/shared virtiofs nosuid,nodev,noatime nofail
diff --git a/shared/config/fstab-erofs.f2fs b/shared/config/fstab-erofs.f2fs
deleted file mode 100644
index 1b5486e..0000000
--- a/shared/config/fstab-erofs.f2fs
+++ /dev/null
@@ -1,18 +0,0 @@
-/dev/block/by-name/boot /boot emmc defaults recoveryonly,slotselect
-/dev/block/by-name/vendor_boot /vendor_boot emmc defaults recoveryonly,slotselect
-system /system erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
-# Add all non-dynamic partitions except system, after this comment
-/dev/block/by-name/userdata /data f2fs nodev,noatime,nosuid,inlinecrypt,reserve_root=32768 latemount,wait,check,quota,formattable,fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized,fscompress,keydirectory=/metadata/vold/metadata_encryption,checkpoint=fs
-/dev/block/by-name/metadata /metadata ext4 nodev,noatime,nosuid,errors=panic wait,formattable,first_stage_mount,check
-/dev/block/by-name/misc /misc emmc defaults defaults
-# Add all dynamic partitions except system, after this comment
-odm /odm erofs ro wait,logical,first_stage_mount,slotselect,avb
-product /product erofs ro wait,logical,first_stage_mount,slotselect,avb
-system_ext /system_ext erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
-vendor /vendor erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta
-vendor_dlkm /vendor_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
-odm_dlkm /odm_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
-/dev/block/zram0 none swap defaults zramsize=75%
-/dev/block/vdc1 /sdcard vfat defaults recoveryonly
-/devices/*/block/vdc auto auto defaults voldmanaged=sdcard1:auto,encryptable=userdata
-shared /mnt/vendor/shared virtiofs nosuid,nodev,noatime nofail
diff --git a/shared/config/fstab.ext4 b/shared/config/fstab.ext4
index 1677941..4d3fe9b 100644
--- a/shared/config/fstab.ext4
+++ b/shared/config/fstab.ext4
@@ -1,17 +1,29 @@
-/dev/block/by-name/boot /boot emmc defaults recoveryonly,slotselect
+# Non-dynamic, boot critical partitions
+/dev/block/by-name/boot /boot emmc defaults recoveryonly,slotselect,first_stage_mount,avb=boot
+/dev/block/by-name/init_boot /init_boot emmc defaults recoveryonly,slotselect,first_stage_mount,avb=init_boot
 /dev/block/by-name/vendor_boot /vendor_boot emmc defaults recoveryonly,slotselect
-system /system ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
+system /system erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system,avb_keys=/avb
+system /system ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta_system,avb_keys=/avb
 # Add all non-dynamic partitions except system, after this comment
 /dev/block/by-name/userdata /data ext4 nodev,noatime,nosuid,errors=panic latemount,wait,check,quota,formattable,fileencryption=aes-256-xts:aes-256-cts,keydirectory=/metadata/vold/metadata_encryption,checkpoint=block
 /dev/block/by-name/metadata /metadata ext4 nodev,noatime,nosuid,errors=panic wait,formattable,first_stage_mount,check
 /dev/block/by-name/misc /misc emmc defaults defaults
 # Add all dynamic partitions except system, after this comment
+odm /odm erofs ro wait,logical,first_stage_mount,slotselect,avb
 odm /odm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+product /product erofs ro wait,logical,first_stage_mount,slotselect,avb
 product /product ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+system_ext /system_ext erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
 system_ext /system_ext ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
+vendor /vendor erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta
 vendor /vendor ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta
+vendor_dlkm /vendor_dlkm erofs ro wait,logical,first_stage_mount,slotselect,avb
 vendor_dlkm /vendor_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+odm_dlkm /odm_dlkm erofs ro wait,logical,first_stage_mount,slotselect,avb
 odm_dlkm /odm_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+system_dlkm /system_dlkm erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta
+system_dlkm /system_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta
+# ZRAM, SD-Card and virtiofs shares
 /dev/block/zram0 none swap defaults zramsize=75%
 /dev/block/vdc1 /sdcard vfat defaults recoveryonly
 /devices/*/block/vdc auto auto defaults voldmanaged=sdcard1:auto,encryptable=userdata
diff --git a/shared/config/fstab.f2fs b/shared/config/fstab.f2fs
index 597e3f0..41162ed 100644
--- a/shared/config/fstab.f2fs
+++ b/shared/config/fstab.f2fs
@@ -1,17 +1,29 @@
-/dev/block/by-name/boot /boot emmc defaults recoveryonly,slotselect
+# Non-dynamic, boot critical partitions
+/dev/block/by-name/boot /boot emmc defaults recoveryonly,slotselect,first_stage_mount,avb=boot
+/dev/block/by-name/init_boot /init_boot emmc defaults recoveryonly,slotselect,first_stage_mount,avb=init_boot
 /dev/block/by-name/vendor_boot /vendor_boot emmc defaults recoveryonly,slotselect
-system /system ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
+system /system erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system,avb_keys=/avb
+system /system ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta_system,avb_keys=/avb
 # Add all non-dynamic partitions except system, after this comment
 /dev/block/by-name/userdata /data f2fs nodev,noatime,nosuid,inlinecrypt,reserve_root=32768 latemount,wait,check,quota,formattable,fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized,fscompress,keydirectory=/metadata/vold/metadata_encryption,checkpoint=fs
 /dev/block/by-name/metadata /metadata ext4 nodev,noatime,nosuid,errors=panic wait,formattable,first_stage_mount,check
 /dev/block/by-name/misc /misc emmc defaults defaults
 # Add all dynamic partitions except system, after this comment
+odm /odm erofs ro wait,logical,first_stage_mount,slotselect,avb
 odm /odm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+product /product erofs ro wait,logical,first_stage_mount,slotselect,avb
 product /product ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+system_ext /system_ext erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
 system_ext /system_ext ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta_system
+vendor /vendor erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta
 vendor /vendor ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta
+vendor_dlkm /vendor_dlkm erofs ro wait,logical,first_stage_mount,slotselect,avb
 vendor_dlkm /vendor_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+odm_dlkm /odm_dlkm erofs ro wait,logical,first_stage_mount,slotselect,avb
 odm_dlkm /odm_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb
+system_dlkm /system_dlkm erofs ro wait,logical,first_stage_mount,slotselect,avb=vbmeta
+system_dlkm /system_dlkm ext4 noatime,ro,errors=panic wait,logical,first_stage_mount,slotselect,avb=vbmeta
+# ZRAM, SD-Card and virtiofs shares
 /dev/block/zram0 none swap defaults zramsize=75%
 /dev/block/vdc1 /sdcard vfat defaults recoveryonly
 /devices/*/block/vdc auto auto defaults voldmanaged=sdcard1:auto,encryptable=userdata
diff --git a/shared/config/grub.cfg b/shared/config/grub.cfg
new file mode 100644
index 0000000..d15eb8e
--- /dev/null
+++ b/shared/config/grub.cfg
@@ -0,0 +1,43 @@
+# Root grub.cfg used either to boot raw kernel and/or initramfs.img, or to
+# chain to an installed distro's GRUB configuration file
+
+# These options are accessible to chain-loaded configurations as well:
+#
+# pnpacpi=off      Disable on QEMU; allows serdev to claim platform serial
+# acpi=noirq       Do not configure IRQ routing using ACPI tables
+# reboot=k         Reboot using keyboard method, rather than ACPI
+# noexec=off       Some kernels panic when setting up NX
+# noefi            Some kernels panic when trying to use U-Boot EFI
+# panic=-1         Don't reboot on panic
+# console=hvc0     Switch kernel logging to virtio-console once available
+# console=ttyAMA0  QEMU on ARM64 uses alternative serial implementation
+#
+if [ "$grub_cpu" = "i386" ]; then
+  set cmdline="pnpacpi=off acpi=noirq reboot=k noexec=off console=ttyS0 noefi panic=-1 console=hvc0"
+elif [ "$grub_cpu" = "arm64" ]; then
+  set cmdline="console=ttyS0 console=ttyAMA0 noefi panic=-1 console=hvc0"
+else
+  echo "Warning: No architecture found for ${grub_cpu}"
+fi
+
+# Root filesystem is on a GUID partition with label "otheros_root"
+set rootfs="/dev/vda14"
+
+# Root filesystem with grub installed
+search --file --set root /boot/grub/grub.cfg --hint (hd0)
+if [ $? = 0 ]; then
+  set prefix=($root)/boot/grub
+  export cmdline
+  export rootfs
+  configfile $prefix/grub.cfg
+  normal_exit
+fi
+
+# Fall back if we couldn't chain to another GRUB install
+set timeout=0
+menuentry "Linux" {
+  linux /vmlinuz $cmdline root=$rootfs
+  if [ -e /initrd.img ]; then
+    initrd /initrd.img
+  fi
+}
diff --git a/shared/config/init.insmod.sh b/shared/config/init.insmod.sh
deleted file mode 100755
index ccbf716..0000000
--- a/shared/config/init.insmod.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/vendor/bin/sh
-
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-KERNEL_VERSION_NUMBER=`uname -r`
-MAINLINE_STR='mainline'
-if [[ $KERNEL_VERSION_NUMBER == *$MAINLINE_STR* ]]; then
-    IS_MAINLINE=1
-else
-    IS_MAINLINE=0
-fi
-
-KERNEL_VERSION_NUMBER=`echo $KERNEL_VERSION_NUMBER | grep -o -E '^[0-9]+\.[0-9]+'`
-# This folder on cuttlefish contains modules for multiple kernel versions.
-# Hence the need to filter them instead of relying on module.order
-VENDOR_MODULES='/vendor/lib/modules/*.ko'
-
-for f in $VENDOR_MODULES
-do
-    MOD_VERSION=`modinfo $f`
-    MOD_VERSION=`echo $MOD_VERSION | grep -o -E 'vermagic: [0-9a-zA-Z\.-]+'`
-    MOD_VERSION_NUMBER=`echo $MOD_VERSION | grep -o -E '[0-9]+\.[0-9]+'`
-    if [[ $MOD_VERSION == *$MAINLINE_STR* ]]; then
-        IS_MOD_MAINLINE=1
-    else
-        IS_MOD_MAINLINE=0
-    fi
-
-    # TODO (137683279) When we have a few more kernel modules, we'll have to do the module
-    # insertion of least dependencies.
-    if [ $IS_MOD_MAINLINE -eq $IS_MAINLINE ] && [ $MOD_VERSION_NUMBER == $KERNEL_VERSION_NUMBER ]
-    then
-        `insmod $f`
-        echo "Insmod " $f
-    fi
-done
diff --git a/shared/config/init.vendor.rc b/shared/config/init.vendor.rc
index 94ef593..8861ca3 100644
--- a/shared/config/init.vendor.rc
+++ b/shared/config/init.vendor.rc
@@ -1,19 +1,19 @@
 on early-init
 #    loglevel 8
 
-    mount securityfs securityfs /sys/kernel/security
-
     setprop ro.sf.lcd_density ${ro.boot.lcd_density}
     setprop ro.hardware.egl ${ro.boot.hardware.egl}
     setprop debug.sf.vsync_reactor_ignore_present_fences true
     setprop ro.hardware.gralloc ${ro.boot.hardware.gralloc}
     setprop ro.hardware.hwcomposer ${ro.boot.hardware.hwcomposer}
+    setprop ro.vendor.hwcomposer.mode ${ro.boot.hardware.hwcomposer.mode}
     setprop ro.hardware.vulkan ${ro.boot.hardware.vulkan}
     setprop ro.cpuvulkan.version ${ro.boot.cpuvulkan.version}
     setprop ro.hw_timeout_multiplier ${ro.boot.hw_timeout_multiplier}
+    setprop ro.opengles.version ${ro.boot.opengles.version}
 
     # start module load in the background
-    start vendor.insmod_sh
+    start vendor.dlkm_loader
 
 on init
     # ZRAM setup
@@ -47,10 +47,6 @@
     mount_all --early
     restorecon_recursive /vendor
 
-    start setup_wifi
-    # works around framework netiface enumeration issue
-    start rename_eth0
-
     # So GceBootReporter can print to kmsg
     chmod 622 /dev/kmsg
 
@@ -58,38 +54,40 @@
     # set RLIMIT_MEMLOCK to 64MB
     setrlimit 8 67108864 67108864
 
-    start bt_vhci_forwarder
+on post-fs-data
+    # works around framework netiface enumeration issue
+    # TODO(b/202731768): Add this `start rename_eth0` command to the init.rc for rename_netiface
+    start rename_eth0
+
+on post-fs-data && property:ro.vendor.wifi_impl=virt_wifi
+    # TODO(b/202731768): Add this `start setup_wifi` command to the init.rc for setup_wifi
+    start setup_wifi
 
 on post-fs-data
     mkdir /data/vendor/modem_dump 0777 system system
     mkdir /data/vendor/radio 0777 system system
 
 on late-fs
-    # Wait for keymaster
-    exec_start wait_for_keymaster
-
     # Mount RW partitions which need run fsck
     mount_all --late
 
     write /dev/kmsg "GUEST_BUILD_FINGERPRINT: ${ro.build.fingerprint}"
 
+on post-fs-data && property:ro.vendor.wifi_impl=mac8011_hwsim_virtio
+    mkdir /data/vendor/wifi 0770 wifi wifi
+    mkdir /data/vendor/wifi/hostapd 0770 wifi wifi
+    mkdir /data/vendor/wifi/hostapd/sockets 0770 wifi wifi
+    start init_wifi_sh
+
 on boot
-    chmod 0660 /dev/cpuctl
+    chmod 0770 /dev/cpuctl
     mkdir /data/vendor/wifi 0770 wifi wifi
     mkdir /data/vendor/wifi/wpa 0770 wifi wifi
     mkdir /data/vendor/wifi/wpa/sockets 0770 wifi wifi
     start socket_vsock_proxy
     setprop ro.hardware.audio.primary goldfish
-
-service bt_vhci_forwarder /vendor/bin/bt_vhci_forwarder -virtio_console_dev=${vendor.ser.bt-uart}
-    user bluetooth
-    group bluetooth
-
-service setup_wifi /vendor/bin/setup_wifi
-    oneshot
-
-service rename_eth0 /vendor/bin/rename_netiface eth0 rmnet0
-    oneshot
+    symlink /dev/hvc6 /dev/gnss0
+    symlink /dev/hvc7 /dev/gnss1
 
 on property:sys.boot_completed=1
     trigger sys-boot-completed-set
@@ -101,7 +99,7 @@
 on sys-boot-completed-set && property:persist.sys.zram_enabled=1
     swapon_all
 
-service vendor.insmod_sh /vendor/bin/init.insmod.sh
+service vendor.dlkm_loader /vendor/bin/dlkm_loader
     class main
     user root
     group root system
@@ -127,17 +125,6 @@
    enable vsoc_input_service
    start vsoc_input_service
 
-service wpa_supplicant /vendor/bin/hw/wpa_supplicant -g@android:wpa_wlan0
-    interface android.hardware.wifi.supplicant@1.0::ISupplicant default
-    interface android.hardware.wifi.supplicant@1.1::ISupplicant default
-    interface android.hardware.wifi.supplicant@1.2::ISupplicant default
-    interface android.hardware.wifi.supplicant@1.3::ISupplicant default
-    interface android.hardware.wifi.supplicant@1.4::ISupplicant default
-    socket wpa_wlan0 dgram 660 wifi wifi
-    group system wifi inet
-    disabled
-    oneshot
-
 service bugreport /system/bin/dumpstate -d -p -z
     class main
     disabled
diff --git a/shared/config/input/Android.bp b/shared/config/input/Android.bp
new file mode 100644
index 0000000..3c85557
--- /dev/null
+++ b/shared/config/input/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_defaults {
+    name: "crosvm_idc_defaults",
+    relative_install_path: "usr/idc",
+    soc_specific: true,
+}
+
+prebuilt_etc {
+    name: "Crosvm_Virtio_Multitouch_Touchscreen_0.idc",
+    src: "Crosvm_Virtio_Multitouch_Touchscreen_0.idc",
+    defaults: ["crosvm_idc_defaults"],
+}
+
+prebuilt_etc {
+    name: "Crosvm_Virtio_Multitouch_Touchscreen_1.idc",
+    src: "Crosvm_Virtio_Multitouch_Touchscreen_1.idc",
+    defaults: ["crosvm_idc_defaults"],
+}
+
+prebuilt_etc {
+    name: "Crosvm_Virtio_Multitouch_Touchscreen_2.idc",
+    src: "Crosvm_Virtio_Multitouch_Touchscreen_2.idc",
+    defaults: ["crosvm_idc_defaults"],
+}
+
+prebuilt_etc {
+    name: "Crosvm_Virtio_Multitouch_Touchscreen_3.idc",
+    src: "Crosvm_Virtio_Multitouch_Touchscreen_3.idc",
+    defaults: ["crosvm_idc_defaults"],
+}
diff --git a/shared/config/manifest.xml b/shared/config/manifest.xml
index 5917647..126557f 100644
--- a/shared/config/manifest.xml
+++ b/shared/config/manifest.xml
@@ -16,7 +16,7 @@
 ** limitations under the License.
 */
 -->
-<manifest version="1.0" type="device" target-level="6">
+<manifest version="1.0" type="device" target-level="7">
     <hal format="hidl">
         <name>android.hardware.audio.effect</name>
         <transport>hwbinder</transport>
@@ -57,15 +57,6 @@
         </interface>
     </hal>
     -->
-    <hal format="hidl">
-        <name>android.hardware.bluetooth.audio</name>
-        <transport>hwbinder</transport>
-        <version>2.1</version>
-        <interface>
-            <name>IBluetoothAudioProvidersFactory</name>
-            <instance>default</instance>
-        </interface>
-    </hal>
     <!-- TODO (b/130078386):
     <hal format="hidl">
         <name>android.hardware.confirmationui</name>
diff --git a/shared/tv/manifest.xml b/shared/config/manifest_android.hardware.graphics.composer@2.4-service.xml
similarity index 60%
rename from shared/tv/manifest.xml
rename to shared/config/manifest_android.hardware.graphics.composer@2.4-service.xml
index 8aab6ee..4d8cc57 100644
--- a/shared/tv/manifest.xml
+++ b/shared/config/manifest_android.hardware.graphics.composer@2.4-service.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
-** Copyright 2017, The Android Open Source Project.
+** Copyright 2022, The Android Open Source Project.
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -17,26 +17,13 @@
 */
 -->
 <manifest version="1.0" type="device">
-     <!-- FIXME: Implement tv.cec HAL
-     <hal format="hidl">
-        <name>android.hardware.tv.cec</name>
-        <transport>hwbinder</transport>
-        <version>1.0</version>
-        <interface>
-            <name>IHdmiCec</name>
-            <instance>default</instance>
-        </interface>
-    </hal>
-    -->
-    <!-- FIXME: Implement tv.input HAL
     <hal format="hidl">
-        <name>android.hardware.tv.input</name>
+        <name>android.hardware.graphics.composer</name>
         <transport>hwbinder</transport>
-        <version>1.0</version>
+        <version>2.4</version>
         <interface>
-            <name>ITvInput</name>
+            <name>IComposer</name>
             <instance>default</instance>
         </interface>
     </hal>
-    -->
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/shared/config/p2p_supplicant.conf b/shared/config/p2p_supplicant.conf
new file mode 100644
index 0000000..2512889
--- /dev/null
+++ b/shared/config/p2p_supplicant.conf
@@ -0,0 +1,7 @@
+disable_scan_offload=1
+update_config=1
+p2p_go_vht=1
+p2p_listen_reg_class=81
+p2p_listen_channel=1
+p2p_oper_reg_class=81
+p2p_oper_channel=1
diff --git a/shared/config/pci.ids b/shared/config/pci.ids
new file mode 100644
index 0000000..790e62b
--- /dev/null
+++ b/shared/config/pci.ids
@@ -0,0 +1,69 @@
+#
+#	List of PCI ID's
+#
+#	Version: 2022.02.09
+#	Date:    2022-02-09 03:15:01
+#
+#	Maintained by Albert Pool, Martin Mares, and other volunteers from
+#	the PCI ID Project at https://pci-ids.ucw.cz/.
+#
+#	New data are always welcome, especially if they are accurate. If you have
+#	anything to contribute, please follow the instructions at the web site.
+#
+#	This file can be distributed under either the GNU General Public License
+#	(version 2 or higher) or the 3-clause BSD License.
+#
+#	The database is a compilation of factual data, and as such the copyright
+#	only covers the aggregation and formatting. The copyright is held by
+#	Martin Mares and Albert Pool.
+#
+
+# Vendors, devices and subsystems. Please keep sorted.
+
+# Syntax:
+# vendor  vendor_name
+#	device  device_name				<-- single tab
+#		subvendor subdevice  subsystem_name	<-- two tabs
+
+# ANDROID:
+# This is a heavily reduced subset of devices put together to work around an
+# issue in toybox lspci. Toybox lspci doesn't fully understand the pci.ids
+# format, and gets stuck in "QEMU Virtual Machine" devices in other sections.
+
+1af4  Red Hat, Inc.
+	1000  Virtio network device
+	1001  Virtio block device
+	1002  Virtio memory balloon
+	1003  Virtio console
+	1004  Virtio SCSI
+	1005  Virtio RNG
+	1009  Virtio filesystem
+# virtio 1.0
+	1041  Virtio network device
+# virtio 1.0
+	1042  Virtio block device
+# virtio 1.0
+	1043  Virtio console
+# virtio 1.0
+	1044  Virtio RNG
+# virtio 1.0
+	1045  Virtio memory balloon
+# virtio 1.0
+	1048  Virtio SCSI
+# virtio 1.0
+	1049  Virtio filesystem
+# virtio 1.0
+	1050  Virtio GPU
+# virtio 1.0
+	1052  Virtio input
+# virtio 1.0
+	1053  Virtio socket
+	105a  Virtio file system
+	1110  Inter-VM shared memory
+		1af4 1100  QEMU Virtual Machine
+1b73  Fresco Logic
+	1000  FL1000G USB 3.0 Host Controller
+		1d5c 1000  Anker USB 3.0 Express Card
+	1009  FL1009 USB 3.0 Host Controller
+	1100  FL1100 USB 3.0 Host Controller
+		16b8 6e31  Allegro Pro USB 3.0 PCIe
diff --git a/shared/config/task_profiles.json b/shared/config/task_profiles.json
index 42366fc..719d352 100644
--- a/shared/config/task_profiles.json
+++ b/shared/config/task_profiles.json
@@ -27,6 +27,19 @@
       ]
     },
     {
+      "Name": "ServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpu",
+            "Path": "system-background"
+          }
+        }
+      ]
+    },
+    {
       "Name": "HighPerformance",
       "Actions": [
         {
@@ -253,59 +266,6 @@
     },
 
     {
-      "Name": "LowIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": "background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "NormalIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "HighIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "MaxIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        }
-      ]
-    },
-
-    {
       "Name": "TimerSlackHigh",
       "Actions": [
         {
@@ -433,6 +393,10 @@
       "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
     },
     {
+      "Name": "SCHED_SP_SYSTEM",
+      "Profiles": [ "ServicePerformance", "LowIoPriority", "TimerSlackNormal" ]
+    },
+    {
       "Name": "SCHED_SP_RT_APP",
       "Profiles": [ "RealtimePerformance", "MaxIoPriority", "TimerSlackNormal" ]
     },
diff --git a/shared/config/ueventd.rc b/shared/config/ueventd.rc
index 8f24201..9af7b8d 100644
--- a/shared/config/ueventd.rc
+++ b/shared/config/ueventd.rc
@@ -9,9 +9,8 @@
 # resume-on-reboot
 /dev/block/pmem0 0770 system system
 
-# vtpm
-/dev/tpm0 0000 root root
-/dev/tpmrm0 000 system system
+# hwcomposer
+/dev/block/pmem1 0770 system system
 
 # seriallogging
 /dev/hvc2 0660 system logd
@@ -26,7 +25,12 @@
 /dev/hvc5 0660 bluetooth bluetooth
 /dev/vhci 0660 bluetooth bluetooth
 
+# gnss hal can access hvc6/hvc7
+/dev/hvc6 0666 system system
+/dev/hvc7 0666 system system
+# gnss0/gnss1 are symlinks of hvc6/hvc7
 /dev/gnss0 0666 system system
+/dev/gnss1 0666 system system
 
 # Factory Reset Protection
 /dev/block/by-name/frp 0660 system system
diff --git a/shared/config/wpa_supplicant.rc b/shared/config/wpa_supplicant.rc
new file mode 100644
index 0000000..50c5faa
--- /dev/null
+++ b/shared/config/wpa_supplicant.rc
@@ -0,0 +1,9 @@
+service wpa_supplicant /vendor/bin/hw/wpa_supplicant \
+        -O/data/vendor/wifi/wpa/sockets -puse_p2p_group_interface=1p2p_device=1 \
+        -m/vendor/etc/wifi/p2p_supplicant.conf \
+        -g@android:wpa_wlan0 -dd
+    interface aidl android.hardware.wifi.supplicant.ISupplicant/default
+    socket wpa_wlan0 dgram 660 wifi wifi
+    group system wifi inet
+    disabled
+    oneshot
diff --git a/shared/device.mk b/shared/device.mk
index ebade7a..04dedbd 100644
--- a/shared/device.mk
+++ b/shared/device.mk
@@ -26,13 +26,27 @@
 # Enforce generic ramdisk allow list
 $(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
 
+# Set Vendor SPL to match platform
+VENDOR_SECURITY_PATCH = $(PLATFORM_SECURITY_PATCH)
+
+# Set boot SPL
+BOOT_SECURITY_PATCH = $(PLATFORM_SECURITY_PATCH)
+
+PRODUCT_VENDOR_PROPERTIES += \
+    ro.vendor.boot_security_patch=$(BOOT_SECURITY_PATCH)
+
 PRODUCT_SOONG_NAMESPACES += device/generic/goldfish-opengl # for vulkan
 PRODUCT_SOONG_NAMESPACES += device/generic/goldfish # for audio and wifi
 
-PRODUCT_SHIPPING_API_LEVEL := 31
+PRODUCT_SHIPPING_API_LEVEL := 33
 PRODUCT_USE_DYNAMIC_PARTITIONS := true
 DISABLE_RILD_OEM_HOOK := true
 
+# TODO(b/205788876) remove this condition when openwrt has an image for arm.
+ifndef PRODUCT_ENFORCE_MAC80211_HWSIM
+PRODUCT_ENFORCE_MAC80211_HWSIM := true
+endif
+
 PRODUCT_SET_DEBUGFS_RESTRICTIONS := true
 
 PRODUCT_SOONG_NAMESPACES += device/generic/goldfish-opengl # for vulkan
@@ -46,13 +60,18 @@
 TARGET_ENABLE_HOST_BLUETOOTH_EMULATION ?= true
 TARGET_USE_BTLINUX_HAL_IMPL ?= true
 
+# TODO(b/65201432): Swiftshader needs to create executable memory.
+PRODUCT_REQUIRES_INSECURE_EXECMEM_FOR_SWIFTSHADER := true
+
 AB_OTA_UPDATER := true
 AB_OTA_PARTITIONS += \
     boot \
+    init_boot \
     odm \
     odm_dlkm \
     product \
     system \
+    system_dlkm \
     system_ext \
     vbmeta \
     vbmeta_system \
@@ -61,7 +80,8 @@
     vendor_dlkm \
 
 # Enable Virtual A/B
-$(call inherit-product, $(SRC_TARGET_DIR)/product/virtual_ab_ota/compression.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/virtual_ab_ota/android_t_baseline.mk)
+PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD := gz
 
 # Enable Scoped Storage related
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulated_storage.mk)
@@ -73,24 +93,31 @@
     persist.adb.tcp.port=5555 \
     ro.com.google.locationfeatures=1 \
     persist.sys.fuse.passthrough.enable=true \
+    persist.sys.fuse.bpf.enable=false \
+
+# Until we support adb keys on user builds, and fix logcat over serial,
+# spawn adbd by default without authorization for "adb logcat"
+ifeq ($(TARGET_BUILD_VARIANT),user)
+PRODUCT_PRODUCT_PROPERTIES += \
+    ro.adb.secure=0 \
+    ro.debuggable=1
+endif
 
 # Explanation of specific properties:
-#   debug.hwui.swap_with_damage avoids boot failure on M http://b/25152138
-#   ro.opengles.version OpenGLES 3.0
 #   ro.hardware.keystore_desede=true needed for CtsKeystoreTestCases
 PRODUCT_VENDOR_PROPERTIES += \
     tombstoned.max_tombstone_count=500 \
     vendor.bt.rootcanal_test_console=off \
-    debug.hwui.swap_with_damage=0 \
     ro.carrier=unknown \
     ro.com.android.dataroaming?=false \
     ro.hardware.virtual_device=1 \
     ro.logd.size=1M \
-    ro.opengles.version=196608 \
     wifi.interface=wlan0 \
+    wifi.direct.interface=p2p-dev-wlan0 \
     persist.sys.zram_enabled=1 \
     ro.hardware.keystore_desede=true \
     ro.rebootescrow.device=/dev/block/pmem0 \
+    ro.vendor.hwcomposer.pmem=/dev/block/pmem1 \
     ro.incremental.enable=1 \
     debug.c2.use_dmabufheaps=1 \
     ro.camerax.extensions.enabled=true \
@@ -157,23 +184,14 @@
 PRODUCT_PACKAGES += \
     CuttlefishService \
     cuttlefish_sensor_injection \
-    rename_netiface \
-    setup_wifi \
-    bt_vhci_forwarder \
     socket_vsock_proxy \
     tombstone_transmit \
     tombstone_producer \
     suspend_blocker \
     vsoc_input_service \
-    vtpm_manager \
 
-SOONG_CONFIG_NAMESPACES += cvd
-SOONG_CONFIG_cvd += launch_configs
-SOONG_CONFIG_cvd_launch_configs += \
-    cvd_config_auto.json \
-    cvd_config_phone.json \
-    cvd_config_tablet.json \
-    cvd_config_tv.json \
+$(call soong_config_append,cvd,launch_configs,cvd_config_auto.json cvd_config_foldable.json cvd_config_go.json cvd_config_phone.json cvd_config_slim.json cvd_config_tablet.json cvd_config_tv.json cvd_config_wear.json)
+$(call soong_config_append,cvd,grub_config,grub.cfg)
 
 #
 # Packages for AOSP-available stuff we use from the framework
@@ -183,7 +201,6 @@
     ip \
     sleep \
     tcpdump \
-    wpa_supplicant \
     wificond \
 
 #
@@ -196,12 +213,6 @@
     libGLESv1_CM_angle \
     libGLESv2_angle
 
-# SwiftShader provides a software-only implementation that is not thread-safe
-PRODUCT_PACKAGES += \
-    libEGL_swiftshader \
-    libGLESv1_CM_swiftshader \
-    libGLESv2_swiftshader
-
 # GL implementation for virgl
 PRODUCT_PACKAGES += \
     libGLES_mesa \
@@ -218,7 +229,6 @@
 
 # GL/Vk implementation for gfxstream
 PRODUCT_PACKAGES += \
-    hwcomposer.ranchu \
     libandroidemu \
     libOpenglCodecCommon \
     libOpenglSystemCommon \
@@ -235,9 +245,19 @@
 #
 PRODUCT_PACKAGES += \
     aidl_lazy_test_server \
-    hidl_lazy_test_server
+    aidl_lazy_cb_test_server \
+    hidl_lazy_test_server \
+    hidl_lazy_cb_test_server
 
-DEVICE_PACKAGE_OVERLAYS := device/google/cuttlefish/shared/overlay
+# 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_AAPT_CONFIG and PRODUCT_AAPT_PREF_CONFIG are intentionally not set to
 # pick up every density resources.
 
@@ -252,6 +272,7 @@
 
 
 ifneq ($(LOCAL_SENSOR_FILE_OVERRIDES),true)
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),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 \
@@ -261,30 +282,12 @@
         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
+endif
 
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
 PRODUCT_COPY_FILES += \
-    hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_back.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_back.json \
-    hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_front.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_front.json \
-    hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_depth.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_depth.json \
-    device/google/cuttlefish/shared/config/init.vendor.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/init.cutf_cvm.rc \
-    device/google/cuttlefish/shared/config/init.product.rc:$(TARGET_COPY_OUT_PRODUCT)/etc/init/init.rc \
-    device/google/cuttlefish/shared/config/ueventd.rc:$(TARGET_COPY_OUT_VENDOR)/ueventd.rc \
-    device/google/cuttlefish/shared/config/media_codecs.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs.xml \
-    device/google/cuttlefish/shared/config/media_codecs_google_video.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_video.xml \
-    device/google/cuttlefish/shared/config/media_codecs_performance.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_performance.xml \
-    device/google/cuttlefish/shared/config/media_profiles.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_profiles_V1_0.xml \
     device/google/cuttlefish/shared/permissions/cuttlefish_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/cuttlefish_excluded_hardware.xml \
-    device/google/cuttlefish/shared/permissions/privapp-permissions-cuttlefish.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/privapp-permissions-cuttlefish.xml \
-    frameworks/av/media/libeffects/data/audio_effects.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_effects.xml \
-    frameworks/av/media/libstagefright/data/media_codecs_google_audio.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_audio.xml \
-    frameworks/av/media/libstagefright/data/media_codecs_google_telephony.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_telephony.xml \
-    frameworks/av/services/audiopolicy/config/r_submix_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/r_submix_audio_policy_configuration.xml \
-    frameworks/av/services/audiopolicy/config/audio_policy_volumes.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_volumes.xml \
-    frameworks/av/services/audiopolicy/config/default_volume_tables.xml:$(TARGET_COPY_OUT_VENDOR)/etc/default_volume_tables.xml \
-    frameworks/av/services/audiopolicy/config/surround_sound_configuration_5_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/surround_sound_configuration_5_0.xml \
     frameworks/native/data/etc/android.hardware.audio.low_latency.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.audio.low_latency.xml \
-    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 \
     frameworks/native/data/etc/android.hardware.camera.concurrent.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.concurrent.xml \
     frameworks/native/data/etc/android.hardware.camera.flash-autofocus.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.flash-autofocus.xml \
     frameworks/native/data/etc/android.hardware.camera.front.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.camera.front.xml \
@@ -296,45 +299,62 @@
     frameworks/native/data/etc/android.hardware.usb.accessory.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.usb.accessory.xml \
     frameworks/native/data/etc/android.hardware.usb.host.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.usb.host.xml \
     frameworks/native/data/etc/android.hardware.wifi.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.wifi.xml \
+    frameworks/native/data/etc/android.hardware.wifi.direct.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.wifi.direct.xml \
     frameworks/native/data/etc/android.hardware.wifi.passpoint.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.wifi.passpoint.xml \
     frameworks/native/data/etc/android.software.ipsec_tunnels.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.ipsec_tunnels.xml \
     frameworks/native/data/etc/android.software.sip.voip.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.sip.voip.xml \
     frameworks/native/data/etc/android.software.verified_boot.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.verified_boot.xml \
-    system/bt/vendor_libs/test_vendor_lib/data/controller_properties.json:vendor/etc/bluetooth/controller_properties.json \
+    hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_back.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_back.json \
+    hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_front.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_front.json \
+    hardware/google/camera/devices/EmulatedCamera/hwl/configs/emu_camera_depth.json:$(TARGET_COPY_OUT_VENDOR)/etc/config/emu_camera_depth.json
+endif
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/android.hardware.consumerir.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.consumerir.xml \
+    device/google/cuttlefish/shared/config/init.vendor.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/init.cutf_cvm.rc \
+    device/google/cuttlefish/shared/config/init.product.rc:$(TARGET_COPY_OUT_PRODUCT)/etc/init/init.rc \
+    device/google/cuttlefish/shared/config/ueventd.rc:$(TARGET_COPY_OUT_VENDOR)/etc/ueventd.rc \
+    device/google/cuttlefish/shared/config/media_codecs.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs.xml \
+    device/google/cuttlefish/shared/config/media_codecs_google_video.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_video.xml \
+    device/google/cuttlefish/shared/config/media_codecs_performance.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_performance.xml \
+    device/google/cuttlefish/shared/config/media_profiles.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_profiles_V1_0.xml \
+    device/google/cuttlefish/shared/permissions/privapp-permissions-cuttlefish.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/privapp-permissions-cuttlefish.xml \
+    frameworks/av/media/libeffects/data/audio_effects.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_effects.xml \
+    frameworks/av/media/libstagefright/data/media_codecs_google_audio.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_audio.xml \
+    frameworks/av/media/libstagefright/data/media_codecs_google_telephony.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_codecs_google_telephony.xml \
+    frameworks/av/services/audiopolicy/config/a2dp_in_audio_policy_configuration_7_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/a2dp_in_audio_policy_configuration_7_0.xml \
+    frameworks/av/services/audiopolicy/config/bluetooth_audio_policy_configuration_7_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/bluetooth_audio_policy_configuration_7_0.xml \
+    frameworks/av/services/audiopolicy/config/r_submix_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/r_submix_audio_policy_configuration.xml \
+    frameworks/av/services/audiopolicy/config/audio_policy_volumes.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_volumes.xml \
+    frameworks/av/services/audiopolicy/config/default_volume_tables.xml:$(TARGET_COPY_OUT_VENDOR)/etc/default_volume_tables.xml \
+    frameworks/av/services/audiopolicy/config/surround_sound_configuration_5_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/surround_sound_configuration_5_0.xml \
     device/google/cuttlefish/shared/config/task_profiles.json:$(TARGET_COPY_OUT_VENDOR)/etc/task_profiles.json \
+
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.cf.input.config
+else
+PRODUCT_COPY_FILES += \
     device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_0.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_0.idc \
     device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_1.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_1.idc \
     device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_2.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_2.idc \
     device/google/cuttlefish/shared/config/input/Crosvm_Virtio_Multitouch_Touchscreen_3.idc:$(TARGET_COPY_OUT_VENDOR)/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_3.idc
+endif
 
-ifeq ($(TARGET_RO_FILE_SYSTEM_TYPE),ext4)
 PRODUCT_COPY_FILES += \
     device/google/cuttlefish/shared/config/fstab.f2fs:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.f2fs \
-    device/google/cuttlefish/shared/config/fstab.f2fs:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/fstab.f2fs \
     device/google/cuttlefish/shared/config/fstab.f2fs:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.f2fs \
     device/google/cuttlefish/shared/config/fstab.f2fs:$(TARGET_COPY_OUT_RECOVERY)/root/first_stage_ramdisk/fstab.f2fs \
     device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.ext4 \
-    device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/fstab.ext4 \
     device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.ext4 \
     device/google/cuttlefish/shared/config/fstab.ext4:$(TARGET_COPY_OUT_RECOVERY)/root/first_stage_ramdisk/fstab.ext4
-else
-PRODUCT_COPY_FILES += \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).f2fs:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.f2fs \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).f2fs:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/fstab.f2fs \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).f2fs:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.f2fs \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).f2fs:$(TARGET_COPY_OUT_RECOVERY)/root/first_stage_ramdisk/fstab.f2fs \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/first_stage_ramdisk/fstab.ext4 \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_VENDOR_RAMDISK)/fstab.ext4 \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.ext4 \
-    device/google/cuttlefish/shared/config/fstab-$(TARGET_RO_FILE_SYSTEM_TYPE).ext4:$(TARGET_COPY_OUT_RECOVERY)/root/first_stage_ramdisk/fstab.ext4
-endif
 
 ifeq ($(TARGET_VULKAN_SUPPORT),true)
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
 PRODUCT_COPY_FILES += \
     frameworks/native/data/etc/android.hardware.vulkan.level-0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.vulkan.level.xml \
     frameworks/native/data/etc/android.hardware.vulkan.version-1_0_3.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.vulkan.version.xml \
-    frameworks/native/data/etc/android.software.vulkan.deqp.level-2021-03-01.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.vulkan.deqp.level.xml \
-    frameworks/native/data/etc/android.software.opengles.deqp.level-2021-03-01.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.opengles.deqp.level.xml
+    frameworks/native/data/etc/android.software.vulkan.deqp.level-2022-03-01.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.vulkan.deqp.level.xml \
+    frameworks/native/data/etc/android.software.opengles.deqp.level-2022-03-01.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.opengles.deqp.level.xml
+endif
 endif
 
 # Packages for HAL implementations
@@ -352,6 +372,12 @@
     android.hardware.weaver-service.example
 
 #
+# IR aidl HAL
+#
+PRODUCT_PACKAGES += \
+	android.hardware.ir-service.example
+
+#
 # OemLock aidl HAL
 #
 PRODUCT_PACKAGES += \
@@ -371,22 +397,32 @@
 #
 # Hardware Composer HAL
 #
+# The device needs to avoid having both hwcomposer2.4 and hwcomposer3
+# services running at the same time so make the user manually enables
+# in order to run with --gpu_mode=drm.
+ifeq ($(TARGET_ENABLE_DRMHWCOMPOSER),true)
+DEVICE_MANIFEST_FILE += \
+    device/google/cuttlefish/shared/config/manifest_android.hardware.graphics.composer@2.4-service.xml
+
 PRODUCT_PACKAGES += \
-    hwcomposer.drm_minigbm \
-    hwcomposer.cutf \
-    hwcomposer-stats \
-    android.hardware.graphics.composer@2.4-service
+    android.hardware.graphics.composer@2.4-service \
+    hwcomposer.drm
+else
+PRODUCT_PACKAGES += \
+    android.hardware.graphics.composer3-service.ranchu
+endif
 
 #
 # Gralloc HAL
 #
 PRODUCT_PACKAGES += \
-    android.hardware.graphics.allocator@4.0-service.minigbm \
+    android.hardware.graphics.allocator-V1-service.minigbm \
     android.hardware.graphics.mapper@4.0-impl.minigbm
 
 #
 # Bluetooth HAL and Compatibility Bluetooth library (for older revs).
 #
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
 ifeq ($(LOCAL_BLUETOOTH_PRODUCT_PACKAGE),)
 ifeq ($(TARGET_ENABLE_HOST_BLUETOOTH_EMULATION),true)
 ifeq ($(TARGET_USE_BTLINUX_HAL_IMPL),true)
@@ -400,27 +436,49 @@
     DEVICE_MANIFEST_FILE += device/google/cuttlefish/shared/config/manifest_android.hardware.bluetooth@1.1-service.xml
 endif
 
+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 += $(LOCAL_BLUETOOTH_PRODUCT_PACKAGE)
 
-PRODUCT_PACKAGES += android.hardware.bluetooth.audio@2.1-impl
+PRODUCT_PACKAGES += android.hardware.bluetooth.audio@2.1-impl  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 android.hardware.bluetooth.audio@2.1-impl
+endif
+
+#
+# Bluetooth Audio AIDL HAL
+#
+PRODUCT_PACKAGES += \
+    android.hardware.bluetooth.audio-impl \
 
 #
 # Audio HAL
 #
-LOCAL_AUDIO_PRODUCT_PACKAGE ?= \
+ifndef LOCAL_AUDIO_PRODUCT_PACKAGE
+LOCAL_AUDIO_PRODUCT_PACKAGE := \
     android.hardware.audio.service \
-    android.hardware.audio@7.0-impl.ranchu \
-    android.hardware.audio.effect@7.0-impl \
+    android.hardware.audio@7.1-impl.ranchu \
+    android.hardware.audio.effect@7.0-impl
+endif
 
-LOCAL_AUDIO_PRODUCT_COPY_FILES ?= \
+ifndef LOCAL_AUDIO_PRODUCT_COPY_FILES
+LOCAL_AUDIO_PRODUCT_COPY_FILES := \
     device/generic/goldfish/audio/policy/audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_configuration.xml \
     device/generic/goldfish/audio/policy/primary_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/primary_audio_policy_configuration.xml \
     frameworks/av/services/audiopolicy/config/r_submix_audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/r_submix_audio_policy_configuration.xml \
     frameworks/av/services/audiopolicy/config/audio_policy_volumes.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_volumes.xml \
     frameworks/av/services/audiopolicy/config/default_volume_tables.xml:$(TARGET_COPY_OUT_VENDOR)/etc/default_volume_tables.xml \
-    frameworks/av/media/libeffects/data/audio_effects.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_effects.xml \
-
-LOCAL_AUDIO_DEVICE_PACKAGE_OVERLAYS ?=
+    frameworks/av/media/libeffects/data/audio_effects.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_effects.xml
+endif
 
 PRODUCT_PACKAGES += $(LOCAL_AUDIO_PRODUCT_PACKAGE)
 PRODUCT_COPY_FILES += $(LOCAL_AUDIO_PRODUCT_COPY_FILES)
@@ -454,20 +512,28 @@
 # Contexthub HAL
 #
 PRODUCT_PACKAGES += \
-    android.hardware.contexthub@1.2-service.mock
+    android.hardware.contexthub-service.example
 
 #
 # Drm HAL
 #
 PRODUCT_PACKAGES += \
-    android.hardware.drm@1.4-service.clearkey \
-    android.hardware.drm@1.4-service.widevine
+    android.hardware.drm@latest-service.clearkey \
+    android.hardware.drm@latest-service.widevine
+
+#
+# Confirmation UI HAL
+#
+ifeq ($(LOCAL_CONFIRMATIONUI_PRODUCT_PACKAGE),)
+    LOCAL_CONFIRMATIONUI_PRODUCT_PACKAGE := android.hardware.confirmationui@1.0-service.cuttlefish
+endif
+PRODUCT_PACKAGES += $(LOCAL_CONFIRMATIONUI_PRODUCT_PACKAGE)
 
 #
 # Dumpstate HAL
 #
 ifeq ($(LOCAL_DUMPSTATE_PRODUCT_PACKAGE),)
-    LOCAL_DUMPSTATE_PRODUCT_PACKAGE := android.hardware.dumpstate@1.1-service.example
+    LOCAL_DUMPSTATE_PRODUCT_PACKAGE += android.hardware.dumpstate-service.example
 endif
 PRODUCT_PACKAGES += $(LOCAL_DUMPSTATE_PRODUCT_PACKAGE)
 
@@ -481,6 +547,10 @@
 DEVICE_MANIFEST_FILE += \
     device/google/cuttlefish/guest/hals/camera/manifest.xml
 else
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.emulated.camera.provider.hal
+PRODUCT_PACKAGES += com.google.emulated.camera.provider.hal.fastscenecycle
+endif
 PRODUCT_PACKAGES += \
     android.hardware.camera.provider@2.7-service-google \
     libgooglecamerahwl_impl \
@@ -491,7 +561,7 @@
 # Gatekeeper
 #
 ifeq ($(LOCAL_GATEKEEPER_PRODUCT_PACKAGE),)
-       LOCAL_GATEKEEPER_PRODUCT_PACKAGE := android.hardware.gatekeeper@1.0-service.software
+       LOCAL_GATEKEEPER_PRODUCT_PACKAGE := android.hardware.gatekeeper@1.0-service.remote
 endif
 PRODUCT_PACKAGES += \
     $(LOCAL_GATEKEEPER_PRODUCT_PACKAGE)
@@ -507,8 +577,9 @@
 # Health
 ifeq ($(LOCAL_HEALTH_PRODUCT_PACKAGE),)
     LOCAL_HEALTH_PRODUCT_PACKAGE := \
-    android.hardware.health@2.1-impl-cuttlefish \
-    android.hardware.health@2.1-service
+    android.hardware.health-service.cuttlefish \
+    android.hardware.health-service.cuttlefish_recovery \
+
 endif
 PRODUCT_PACKAGES += $(LOCAL_HEALTH_PRODUCT_PACKAGE)
 
@@ -518,25 +589,36 @@
 
 # Identity Credential
 PRODUCT_PACKAGES += \
-    android.hardware.identity-service.example
+    android.hardware.identity-service.remote
 
-# Input Classifier HAL
 PRODUCT_PACKAGES += \
-    android.hardware.input.classifier@1.0-service.default
+    android.hardware.input.processor-service.example
+
+# Netlink Interceptor HAL
+PRODUCT_PACKAGES += \
+    android.hardware.net.nlinterceptor-service.default
 
 #
 # Sensors
 #
 ifeq ($(LOCAL_SENSOR_PRODUCT_PACKAGE),)
-       LOCAL_SENSOR_PRODUCT_PACKAGE := android.hardware.sensors@2.1-service.mock
+# TODO(b/210883464): Convert the sensors APEX to use the new AIDL impl.
+#ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+#       LOCAL_SENSOR_PRODUCT_PACKAGE := com.android.hardware.sensors
+#else
+       LOCAL_SENSOR_PRODUCT_PACKAGE := android.hardware.sensors-service.example
+#endif
 endif
 PRODUCT_PACKAGES += \
     $(LOCAL_SENSOR_PRODUCT_PACKAGE)
 #
 # Thermal (mock)
 #
-PRODUCT_PACKAGES += \
-    android.hardware.thermal@2.0-service.mock
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.android.hardware.thermal.mock
+else
+PRODUCT_PACKAGES += android.hardware.thermal@2.0-service.mock
+endif
 
 #
 # Lights
@@ -548,51 +630,66 @@
 # KeyMint HAL
 #
 ifeq ($(LOCAL_KEYMINT_PRODUCT_PACKAGE),)
-       LOCAL_KEYMINT_PRODUCT_PACKAGE := android.hardware.security.keymint-service
+       LOCAL_KEYMINT_PRODUCT_PACKAGE := android.hardware.security.keymint-service.remote
+# Indicate that this KeyMint includes support for the ATTEST_KEY key purpose.
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/android.hardware.keystore.app_attest_key.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.keystore.app_attest_key.xml
 endif
  PRODUCT_PACKAGES += \
     $(LOCAL_KEYMINT_PRODUCT_PACKAGE)
 
 # Keymint configuration
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
 PRODUCT_COPY_FILES += \
     frameworks/native/data/etc/android.software.device_id_attestation.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.device_id_attestation.xml
+endif
 
 #
-# Power HAL
+# Dice HAL
 #
 PRODUCT_PACKAGES += \
-    android.hardware.power-service.example
+    android.hardware.security.dice-service.non-secure-software
 
 #
-# PowerStats HAL
+# Power and PowerStats HALs
 #
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.android.hardware.power
+else
 PRODUCT_PACKAGES += \
-    android.hardware.power.stats-service.example
+    android.hardware.power-service.example \
+    android.hardware.power.stats-service.example \
+
+endif
 
 #
 # NeuralNetworks HAL
 #
 PRODUCT_PACKAGES += \
     android.hardware.neuralnetworks@1.3-service-sample-all \
-    android.hardware.neuralnetworks@1.3-service-sample-float-fast \
-    android.hardware.neuralnetworks@1.3-service-sample-float-slow \
-    android.hardware.neuralnetworks@1.3-service-sample-minimal \
-    android.hardware.neuralnetworks@1.3-service-sample-quant \
+    android.hardware.neuralnetworks@1.3-service-sample-limited \
     android.hardware.neuralnetworks-service-sample-all \
-    android.hardware.neuralnetworks-service-sample-float-fast \
-    android.hardware.neuralnetworks-service-sample-float-slow \
-    android.hardware.neuralnetworks-service-sample-minimal \
-    android.hardware.neuralnetworks-service-sample-quant \
+    android.hardware.neuralnetworks-service-sample-limited \
     android.hardware.neuralnetworks-shim-service-sample
 
 #
 # USB
+# TODO(b/227791019): Convert USB AIDL HAL to APEX
+# ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+# PRODUCT_PACKAGES += \
+#    com.android.hardware.usb
+#else
 PRODUCT_PACKAGES += \
-    android.hardware.usb@1.0-service
+    android.hardware.usb-service.example
+#endif
 
 # Vibrator HAL
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.android.hardware.vibrator
+else
 PRODUCT_PACKAGES += \
     android.hardware.vibrator-service.example
+endif
 
 # BootControl HAL
 PRODUCT_PACKAGES += \
@@ -608,26 +705,6 @@
 PRODUCT_PACKAGES += \
     android.hardware.memtrack-service.example
 
-# GKI APEX
-# Keep in sync with BOARD_KERNEL_MODULE_INTERFACE_VERSIONS
-ifneq (,$(TARGET_KERNEL_USE))
-  ifneq (,$(filter 5.4, $(TARGET_KERNEL_USE)))
-    PRODUCT_PACKAGES += com.android.gki.kmi_5_4_android12_unstable
-  else
-    PRODUCT_PACKAGES += com.android.gki.kmi_$(subst .,_,$(TARGET_KERNEL_USE))_android12_unstable
-  endif
-endif
-
-# Prevent GKI and boot image downgrades
-PRODUCT_PRODUCT_PROPERTIES += \
-    ro.build.ab_update.gki.prevent_downgrade_version=true \
-    ro.build.ab_update.gki.prevent_downgrade_spl=true \
-
-# WLAN driver configuration files
-PRODUCT_COPY_FILES += \
-    external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant_template.conf:$(TARGET_COPY_OUT_VENDOR)/etc/wifi/wpa_supplicant.conf \
-    $(LOCAL_PATH)/config/wpa_supplicant_overlay.conf:$(TARGET_COPY_OUT_VENDOR)/etc/wifi/wpa_supplicant_overlay.conf
-
 # Fastboot HAL & fastbootd
 PRODUCT_PACKAGES += \
     android.hardware.fastboot@1.1-impl-mock \
@@ -653,16 +730,88 @@
 PRODUCT_PACKAGES += linker.recovery shell_and_utilities_recovery
 endif
 
+# wifi
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+ifneq ($(PRODUCT_ENFORCE_MAC80211_HWSIM),true)
+PRODUCT_PACKAGES += com.google.cf.wifi
+# Demonstrate multi-installed vendor APEXes by installing another wifi HAL vendor APEX
+# which does not include the passpoint feature XML.
 #
-# Shell script Vendor Module Loading
-#
+# The default is set in BoardConfig.mk using bootconfig.
+# This can be changed at CVD launch-time using
+#     --extra_bootconfig_args "androidboot.vendor.apex.com.android.wifi.hal:=X"
+# or post-launch, at runtime using
+#     setprop persist.vendor.apex.com.android.wifi.hal X && reboot
+# where X is the name of the APEX file to use.
+PRODUCT_PACKAGES += com.google.cf.wifi.no-passpoint
+
+$(call add_soong_config_namespace, wpa_supplicant)
+$(call add_soong_config_var_value, wpa_supplicant, platform_version, $(PLATFORM_VERSION))
+$(call add_soong_config_var_value, wpa_supplicant, nl80211_driver, CONFIG_DRIVER_NL80211_QCA)
+PRODUCT_VENDOR_PROPERTIES += ro.vendor.wifi_impl=virt_wifi
+else
+PRODUCT_SOONG_NAMESPACES += device/google/cuttlefish/apex/com.google.cf.wifi_hwsim
+PRODUCT_PACKAGES += com.google.cf.wifi_hwsim
+$(call add_soong_config_namespace, wpa_supplicant)
+$(call add_soong_config_var_value, wpa_supplicant, platform_version, $(PLATFORM_VERSION))
+$(call add_soong_config_var_value, wpa_supplicant, nl80211_driver, CONFIG_DRIVER_NL80211_QCA)
+PRODUCT_VENDOR_PROPERTIES += ro.vendor.wifi_impl=mac8011_hwsim_virtio
+
+$(call soong_config_append,cvdhost,enforce_mac80211_hwsim,true)
+endif
+else
+
+PRODUCT_PACKAGES += \
+    rename_netiface \
+    wpa_supplicant
 PRODUCT_COPY_FILES += \
-   $(LOCAL_PATH)/config/init.insmod.sh:$(TARGET_COPY_OUT_VENDOR)/bin/init.insmod.sh \
+    device/google/cuttlefish/shared/config/wpa_supplicant.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/wpa_supplicant.rc
+
+# WLAN driver configuration files
+ifndef LOCAL_WPA_SUPPLICANT_OVERLAY
+LOCAL_WPA_SUPPLICANT_OVERLAY := $(LOCAL_PATH)/config/wpa_supplicant_overlay.conf
+endif
+ifndef LOCAL_P2P_SUPPLICANT
+LOCAL_P2P_SUPPLICANT := $(LOCAL_PATH)/config/p2p_supplicant.conf
+endif
+PRODUCT_COPY_FILES += \
+    external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant_template.conf:$(TARGET_COPY_OUT_VENDOR)/etc/wifi/wpa_supplicant.conf \
+    $(LOCAL_WPA_SUPPLICANT_OVERLAY):$(TARGET_COPY_OUT_VENDOR)/etc/wifi/wpa_supplicant_overlay.conf \
+    $(LOCAL_P2P_SUPPLICANT):$(TARGET_COPY_OUT_VENDOR)/etc/wifi/p2p_supplicant.conf
+
+ifeq ($(PRODUCT_ENFORCE_MAC80211_HWSIM),true)
+PRODUCT_PACKAGES += \
+    mac80211_create_radios \
+    hostapd \
+    android.hardware.wifi@1.0-service \
+    init.wifi.sh
+
+PRODUCT_VENDOR_PROPERTIES += ro.vendor.wifi_impl=mac8011_hwsim_virtio
+
+$(call soong_config_append,cvdhost,enforce_mac80211_hwsim,true)
+
+else
+PRODUCT_PACKAGES += setup_wifi
+PRODUCT_VENDOR_PROPERTIES += ro.vendor.wifi_impl=virt_wifi
+endif
+
+endif
+
+# UWB HAL
+PRODUCT_PACKAGES += \
+    android.hardware.uwb-service
+
+ifeq ($(PRODUCT_ENFORCE_MAC80211_HWSIM),true)
+# Wifi Runtime Resource Overlay
+PRODUCT_PACKAGES += \
+    CuttlefishTetheringOverlay \
+    CuttlefishWifiOverlay
+endif
 
 # Host packages to install
 PRODUCT_HOST_PACKAGES += socket_vsock_proxy
 
-PRODUCT_EXTRA_VNDK_VERSIONS := 28 29 30
+PRODUCT_EXTRA_VNDK_VERSIONS := 28 29 30 31
 
 PRODUCT_SOONG_NAMESPACES += external/mesa3d
 
@@ -674,6 +823,10 @@
 PRODUCT_VENDOR_PROPERTIES += \
     ro.surface_flinger.running_without_sync_framework=true
 
+# Enable GPU-intensive background blur support on Cuttlefish when requested by apps
+PRODUCT_VENDOR_PROPERTIES += \
+    ro.surface_flinger.supports_background_blur 1
+
 # Set support one-handed mode
 PRODUCT_PRODUCT_PROPERTIES += \
     ro.support_one_handed_mode=true
@@ -685,3 +838,14 @@
 # Set one_handed_mode translate animation duration milliseconds
 PRODUCT_PRODUCT_PROPERTIES += \
     persist.debug.one_handed_translate_animation_duration=300
+
+# Vendor Dlkm Locader
+PRODUCT_PACKAGES += \
+   dlkm_loader
+
+# NFC AIDL HAL
+PRODUCT_PACKAGES += \
+    android.hardware.nfc-service.cuttlefish
+
+PRODUCT_COPY_FILES += \
+    device/google/cuttlefish/shared/config/pci.ids:$(TARGET_COPY_OUT_VENDOR)/pci.ids
diff --git a/shared/foldable/overlay/frameworks/base/core/res/res/values/config.xml b/shared/foldable/overlay/frameworks/base/core/res/res/values/config.xml
index 918d39a..b0b42ab 100644
--- a/shared/foldable/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/shared/foldable/overlay/frameworks/base/core/res/res/values/config.xml
@@ -32,6 +32,8 @@
   </integer-array>
   <!-- Indicates whether to enable an animation when unfolding a device or not -->
   <bool name="config_unfoldTransitionEnabled">true</bool>
+  <!-- Indicates whether to enable hinge angle sensor when using unfold animation -->
+  <bool name="config_unfoldTransitionHingeAngle">true</bool>
   <bool name="config_supportsConcurrentInternalDisplays">false</bool>
   <!-- Controls whether the device support multi window modes like split-screen. -->
   <bool name="config_supportsMultiWindow">true</bool>
diff --git a/shared/go/OWNERS b/shared/go/OWNERS
new file mode 100644
index 0000000..0c77d0e
--- /dev/null
+++ b/shared/go/OWNERS
@@ -0,0 +1,2 @@
+rajekumar@google.com
+tjoines@google.com
diff --git a/shared/go/android-info.txt b/shared/go/android-info.txt
new file mode 100644
index 0000000..68f4826
--- /dev/null
+++ b/shared/go/android-info.txt
@@ -0,0 +1 @@
+config=go
diff --git a/shared/go/device.mk b/shared/go/device.mk
deleted file mode 100644
index e165923..0000000
--- a/shared/go/device.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, build/target/product/go_defaults.mk)
-$(call inherit-product, device/google/cuttlefish/shared/phone/device.mk)
-
-# By default, enable zram; experiment can toggle the flag,
-# which takes effect on boot
-PRODUCT_VENDOR_PROPERTIES += \
-    pm.dexopt.downgrade_after_inactive_days=10 \
-    pm.dexopt.shared=quicken \
-    dalvik.vm.heapgrowthlimit=128m \
-    dalvik.vm.heapsize=256m \
diff --git a/shared/go/device_vendor.mk b/shared/go/device_vendor.mk
new file mode 100644
index 0000000..85d64dd
--- /dev/null
+++ b/shared/go/device_vendor.mk
@@ -0,0 +1,47 @@
+#
+# 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.
+#
+
+PRODUCT_MANIFEST_FILES += device/google/cuttlefish/shared/config/product_manifest.xml
+SYSTEM_EXT_MANIFEST_FILES += device/google/cuttlefish/shared/config/system_ext_manifest.xml
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_vendor.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_vendor.mk)
+
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/go_handheld_core_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/go_handheld_core_hardware.xml
+
+$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
+$(call inherit-product, device/google/cuttlefish/shared/device.mk)
+
+PRODUCT_VENDOR_PROPERTIES += \
+    keyguard.no_require_sim=true \
+    ro.cdma.home.operator.alpha=Android \
+    ro.cdma.home.operator.numeric=302780 \
+    ro.com.android.dataroaming=true \
+    ro.telephony.default_network=9 \
+
+TARGET_USES_CF_RILD ?= true
+ifeq ($(TARGET_USES_CF_RILD),true)
+PRODUCT_PACKAGES += \
+    libcuttlefish-ril-2 \
+    libcuttlefish-rild
+endif
+
+PRODUCT_PACKAGES += \
+    cuttlefish_phone_overlay_frameworks_base_core \
+    cuttlefish_go_phone_overlay_frameworks_base_core \
+
+TARGET_BOARD_INFO_FILE ?= device/google/cuttlefish/shared/go/android-info.txt
diff --git a/shared/go/overlays/core/Android.bp b/shared/go/overlays/core/Android.bp
new file mode 100644
index 0000000..b46bcce
--- /dev/null
+++ b/shared/go/overlays/core/Android.bp
@@ -0,0 +1,8 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+    name: "cuttlefish_go_phone_overlay_frameworks_base_core",
+    soc_specific: true,
+}
diff --git a/shared/go/overlays/core/AndroidManifest.xml b/shared/go/overlays/core/AndroidManifest.xml
new file mode 100644
index 0000000..d51da61
--- /dev/null
+++ b/shared/go/overlays/core/AndroidManifest.xml
@@ -0,0 +1,10 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cuttlefish.go_phone.overlay">
+
+    <application android:hasCode="false" />
+
+    <overlay
+      android:targetPackage="android"
+      android:isStatic="true"
+      />
+</manifest>
diff --git a/shared/go/overlays/core/res/values/config.xml b/shared/go/overlays/core/res/values/config.xml
new file mode 100644
index 0000000..da1dca2
--- /dev/null
+++ b/shared/go/overlays/core/res/values/config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- True if the device requires AppWidgetService even if it does not have
+         the PackageManager.FEATURE_APP_WIDGETS feature -->
+    <bool name="config_enableAppWidgetService">true</bool>
+
+    <!-- circle shape icon mask to be used for {@link AdaptiveIconDrawable} -->
+    <string name="config_icon_mask" translatable="false">"M50 0C77.6 0 100 22.4 100 50C100 77.6 77.6 100 50 100C22.4 100 0 77.6 0 50C0 22.4 22.4 0 50 0Z"</string>
+</resources>
diff --git a/shared/go_512/device.mk b/shared/go_512/device.mk
deleted file mode 100644
index 0ab601b..0000000
--- a/shared/go_512/device.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, build/target/product/go_defaults_512.mk)
-$(call inherit-product, device/google/cuttlefish/shared/phone/device.mk)
-
-# By default, enable zram; experiment can toggle the flag,
-# which takes effect on boot
-PRODUCT_VENDOR_PROPERTIES += \
-    pm.dexopt.downgrade_after_inactive_days=10 \
-    pm.dexopt.shared=quicken \
-    dalvik.vm.heapgrowthlimit=128m \
-    dalvik.vm.heapsize=256m \
diff --git a/shared/overlays/SettingsProvider/Android.bp b/shared/overlays/SettingsProvider/Android.bp
new file mode 100644
index 0000000..6072a34
--- /dev/null
+++ b/shared/overlays/SettingsProvider/Android.bp
@@ -0,0 +1,8 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+    name: "cuttlefish_overlay_settings_provider",
+    soc_specific: true,
+}
diff --git a/shared/overlays/SettingsProvider/AndroidManifest.xml b/shared/overlays/SettingsProvider/AndroidManifest.xml
new file mode 100644
index 0000000..158fb01
--- /dev/null
+++ b/shared/overlays/SettingsProvider/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.providers.settings.cuttlefish.overlay">
+
+    <application android:hasCode="false" />
+
+    <overlay
+      android:targetPackage="com.android.providers.settings"
+      android:isStatic="true"
+      android:priority="1"
+      />
+</manifest>
diff --git a/shared/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xml b/shared/overlays/SettingsProvider/res/values/defaults.xml
similarity index 100%
rename from shared/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xml
rename to shared/overlays/SettingsProvider/res/values/defaults.xml
diff --git a/shared/overlays/connectivity/Android.bp b/shared/overlays/connectivity/Android.bp
new file mode 100644
index 0000000..58b2a02
--- /dev/null
+++ b/shared/overlays/connectivity/Android.bp
@@ -0,0 +1,10 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+    name: "cuttlefish_overlay_connectivity",
+    resource_dirs: ["res"],
+    vendor: true,
+    sdk_version: "current",
+}
diff --git a/shared/overlays/connectivity/AndroidManifest.xml b/shared/overlays/connectivity/AndroidManifest.xml
new file mode 100644
index 0000000..000f0ba
--- /dev/null
+++ b/shared/overlays/connectivity/AndroidManifest.xml
@@ -0,0 +1,12 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.connectivity.resources.cuttlefish.overlay">
+
+    <application android:hasCode="false" />
+
+    <overlay
+      android:targetPackage="com.android.connectivity.resources"
+      android:targetName="ServiceConnectivityResourcesConfig"
+      android:isStatic="true"
+      android:priority="1"
+      />
+</manifest>
diff --git a/shared/overlays/connectivity/res/values/config.xml b/shared/overlays/connectivity/res/values/config.xml
new file mode 100644
index 0000000..ead8b95
--- /dev/null
+++ b/shared/overlays/connectivity/res/values/config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, The Android Open Source Project.
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Restricting eth1 for auto/pc/phone/tv -->
+    <string-array translatable="false" name="config_ethernet_interfaces">
+        <item>eth1;11,12,14;;</item>
+    </string-array>
+</resources>
diff --git a/shared/overlays/core/Android.bp b/shared/overlays/core/Android.bp
new file mode 100644
index 0000000..e297776
--- /dev/null
+++ b/shared/overlays/core/Android.bp
@@ -0,0 +1,8 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+    name: "cuttlefish_overlay_frameworks_base_core",
+    soc_specific: true,
+}
diff --git a/shared/overlays/core/AndroidManifest.xml b/shared/overlays/core/AndroidManifest.xml
new file mode 100644
index 0000000..a236be9
--- /dev/null
+++ b/shared/overlays/core/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cuttlefish.overlay">
+
+    <application android:hasCode="false" />
+
+    <overlay
+      android:targetPackage="android"
+      android:isStatic="true"
+      android:priority="1"
+      />
+</manifest>
diff --git a/shared/overlay/frameworks/base/core/res/res/xml/power_profile.xml b/shared/overlays/core/res/xml/power_profile.xml
similarity index 100%
rename from shared/overlay/frameworks/base/core/res/res/xml/power_profile.xml
rename to shared/overlays/core/res/xml/power_profile.xml
diff --git a/shared/pc/overlay/frameworks/base/core/res/res/values/config.xml b/shared/pc/overlay/frameworks/base/core/res/res/values/config.xml
index 313712f..ce0ba2b 100644
--- a/shared/pc/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/shared/pc/overlay/frameworks/base/core/res/res/values/config.xml
@@ -20,8 +20,4 @@
   <bool name="config_showNavigationBar" translatable="false">true</bool>
   <!--  Maximum number of supported users -->
   <integer name="config_multiuserMaximumUsers" translatable="false">4</integer>
-  <!-- Restricting eth1 -->
-  <string-array translatable="false" name="config_ethernet_interfaces">
-    <item>eth1;11,12,14;;</item>
-  </string-array>
 </resources>
diff --git a/shared/permissions/Android.bp b/shared/permissions/Android.bp
new file mode 100644
index 0000000..1b1e7bb
--- /dev/null
+++ b/shared/permissions/Android.bp
@@ -0,0 +1,10 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+    name: "cuttlefish_excluded_hardware.prebuilt.xml",
+    src: "cuttlefish_excluded_hardware.xml",
+    relative_install_path: "permissions",
+    soc_specific: true,
+}
diff --git a/shared/phone/device.mk b/shared/phone/device.mk
deleted file mode 100644
index faa2c7c..0000000
--- a/shared/phone/device.mk
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-PRODUCT_MANIFEST_FILES += device/google/cuttlefish/shared/config/product_manifest.xml
-SYSTEM_EXT_MANIFEST_FILES += device/google/cuttlefish/shared/config/system_ext_manifest.xml
-
-$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_base_telephony.mk)
-$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
-$(call inherit-product, device/google/cuttlefish/shared/device.mk)
-
-PRODUCT_VENDOR_PROPERTIES += \
-    keyguard.no_require_sim=true \
-    ro.cdma.home.operator.alpha=Android \
-    ro.cdma.home.operator.numeric=302780 \
-    ro.telephony.default_network=9 \
-
-PRODUCT_PACKAGES += \
-    MmsService \
-    Phone \
-    PhoneService \
-    Telecom \
-    TeleService \
-    libcuttlefish-ril-2 \
-    libcuttlefish-rild
-
-PRODUCT_COPY_FILES += \
-    frameworks/native/data/etc/android.hardware.faketouch.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.faketouch.xml \
-    frameworks/native/data/etc/android.hardware.telephony.gsm.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.telephony.gsm.xml
-
-DEVICE_PACKAGE_OVERLAYS += device/google/cuttlefish/shared/phone/overlay
-
-# These flags are important for the GSI, but break auto
-# These are used by aosp_cf_x86_go_phone targets
-PRODUCT_ENFORCE_RRO_TARGETS := framework-res
-
-# Storage: for factory reset protection feature
-PRODUCT_VENDOR_PROPERTIES += \
-    ro.frp.pst=/dev/block/by-name/frp
diff --git a/shared/phone/device_vendor.mk b/shared/phone/device_vendor.mk
index 61f72c1..f373136 100644
--- a/shared/phone/device_vendor.mk
+++ b/shared/phone/device_vendor.mk
@@ -20,12 +20,16 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_vendor.mk)
 
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
 PRODUCT_COPY_FILES += \
     frameworks/native/data/etc/handheld_core_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/handheld_core_hardware.xml
+endif
 
 $(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
 $(call inherit-product, device/google/cuttlefish/shared/device.mk)
 
+TARGET_PRODUCT_PROP := $(LOCAL_PATH)/product.prop
+
 PRODUCT_VENDOR_PROPERTIES += \
     keyguard.no_require_sim=true \
     ro.cdma.home.operator.alpha=Android \
@@ -33,22 +37,31 @@
     ro.com.android.dataroaming=true \
     ro.telephony.default_network=9 \
 
-# TODO: not existing anymore?
-PRODUCT_PACKAGES += \
-    Phone \
-    PhoneService \
-
+TARGET_USES_CF_RILD ?= true
+ifeq ($(TARGET_USES_CF_RILD),true)
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.cf.rild
+else
 PRODUCT_PACKAGES += \
     libcuttlefish-ril-2 \
     libcuttlefish-rild
+endif
+endif
 
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
 PRODUCT_COPY_FILES += \
     frameworks/native/data/etc/android.hardware.biometrics.face.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.biometrics.face.xml \
     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 \
     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
+endif
 
-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
 
 TARGET_BOARD_INFO_FILE ?= device/google/cuttlefish/shared/phone/android-info.txt
diff --git a/shared/phone/overlays/CuttlefishTetheringOverlay/Android.bp b/shared/phone/overlays/CuttlefishTetheringOverlay/Android.bp
new file mode 100644
index 0000000..a35bfd2
--- /dev/null
+++ b/shared/phone/overlays/CuttlefishTetheringOverlay/Android.bp
@@ -0,0 +1,10 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+    name: "CuttlefishTetheringOverlay",
+    resource_dirs: ["res"],
+    vendor: true,
+    sdk_version: "current",
+}
diff --git a/shared/phone/overlays/CuttlefishTetheringOverlay/AndroidManifest.xml b/shared/phone/overlays/CuttlefishTetheringOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..467d0ef
--- /dev/null
+++ b/shared/phone/overlays/CuttlefishTetheringOverlay/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<!--
+  Copyright (C) 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.networkstack.tethering.cuttlefishoverlay"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <application android:hasCode="false" />
+
+    <overlay
+        android:targetPackage="com.android.networkstack.tethering"
+        android:targetName="TetheringConfig"
+        android:priority="0"
+        android:isStatic="true"/>
+
+</manifest>
\ No newline at end of file
diff --git a/shared/phone/overlays/CuttlefishTetheringOverlay/res/values/config.xml b/shared/phone/overlays/CuttlefishTetheringOverlay/res/values/config.xml
new file mode 100644
index 0000000..00b6735
--- /dev/null
+++ b/shared/phone/overlays/CuttlefishTetheringOverlay/res/values/config.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, The Android Open Source Project.
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string-array name="config_tether_wifi_regexs">
+      <item>"wlan\\d"</item>
+    </string-array>
+    <string-array name="config_tether_wifi_p2p_regexs">
+      <item>"p2p-wlan\\d-.*"</item>
+      <item>"p2p-dev-wlan\\d-.*"</item>
+      <item>"p2p\\d"</item>
+      <item>"p2p-p2p\\d-.*"</item>
+    </string-array>
+</resources>
diff --git a/shared/phone/overlays/CuttlefishWifiOverlay/Android.bp b/shared/phone/overlays/CuttlefishWifiOverlay/Android.bp
new file mode 100644
index 0000000..fd2f7eb
--- /dev/null
+++ b/shared/phone/overlays/CuttlefishWifiOverlay/Android.bp
@@ -0,0 +1,10 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+    name: "CuttlefishWifiOverlay",
+    resource_dirs: ["res"],
+    vendor: true,
+    sdk_version: "current",
+}
diff --git a/shared/phone/overlays/CuttlefishWifiOverlay/AndroidManifest.xml b/shared/phone/overlays/CuttlefishWifiOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..bc29c5c
--- /dev/null
+++ b/shared/phone/overlays/CuttlefishWifiOverlay/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<!--
+  Copyright (C) 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wifi.resources.cf"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <application android:hasCode="false" />
+
+    <overlay
+        android:targetPackage="com.android.wifi.resources"
+      android:targetName="WifiCustomization"
+        android:priority="0"
+        android:isStatic="true"/>
+
+</manifest>
\ No newline at end of file
diff --git a/shared/phone/overlays/CuttlefishWifiOverlay/res/values/config.xml b/shared/phone/overlays/CuttlefishWifiOverlay/res/values/config.xml
new file mode 100644
index 0000000..5586326
--- /dev/null
+++ b/shared/phone/overlays/CuttlefishWifiOverlay/res/values/config.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, The Android Open Source Project.
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- True if the firmware supports connected MAC randomization -->
+    <!-- TODO(b/223101490) Disable temporarily for Wi-Fi connection issue -->
+    <bool name="config_wifi_connected_mac_randomization_supported">false</bool>
+
+    <!-- True if the firmware supports p2p MAC randomization -->
+    <bool name="config_wifi_p2p_mac_randomization_supported">true</bool>
+
+    <!-- True if the firmware supports ap MAC randomization -->
+    <bool name="config_wifi_ap_mac_randomization_supported">true</bool>
+
+</resources>
diff --git a/shared/phone/overlays/core/Android.bp b/shared/phone/overlays/core/Android.bp
new file mode 100644
index 0000000..23fddab
--- /dev/null
+++ b/shared/phone/overlays/core/Android.bp
@@ -0,0 +1,8 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+    name: "cuttlefish_phone_overlay_frameworks_base_core",
+    soc_specific: true,
+}
diff --git a/shared/phone/overlays/core/AndroidManifest.xml b/shared/phone/overlays/core/AndroidManifest.xml
new file mode 100644
index 0000000..5ab8d7e
--- /dev/null
+++ b/shared/phone/overlays/core/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cuttlefish.phone.overlay">
+
+    <application android:hasCode="false" />
+
+    <overlay
+      android:targetPackage="android"
+      android:isStatic="true"
+      android:priority="2"
+      />
+</manifest>
diff --git a/shared/phone/overlay/frameworks/base/core/res/res/values/config.xml b/shared/phone/overlays/core/res/values/config.xml
similarity index 92%
rename from shared/phone/overlay/frameworks/base/core/res/res/values/config.xml
rename to shared/phone/overlays/core/res/values/config.xml
index 61feabf..1d7b019 100644
--- a/shared/phone/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/shared/phone/overlays/core/res/values/config.xml
@@ -55,10 +55,6 @@
   <string name="config_mms_user_agent_profile_url" translatable="false">http://gsm.lge.com/html/gsm/Nexus5-M3.xml</string>
   <string name="config_wlan_data_service_package" translatable="false">com.android.ims</string>
   <string name="config_wlan_network_service_package" translatable="false">com.android.ims</string>
-  <!-- Restricting eth1 -->
-  <string-array translatable="false" name="config_ethernet_interfaces">
-    <item>eth1;11,12,14;;</item>
-  </string-array>
 
   <!-- List of biometric sensors on the device, in decreasing strength. Consumed by AuthService
   when registering authenticators with BiometricService. Format must be ID:Modality:Strength,
@@ -68,4 +64,9 @@
     <item>2:2:255</item> <!-- ID2:Fingerprint(HIDL):Weak -->
     <item>3:8:255</item> <!-- ID3:Face(HIDL):Weak -->
   </string-array>
+
+  <!-- Enable Night display, which requires HWC 2.0. -->
+  <bool name="config_nightDisplayAvailable">true</bool>
+  <!-- Let ColorFade use a color layer to avoid deadlocking in WM CTS. See b/233386717. -->
+  <bool name="config_animateScreenLights">true</bool>
 </resources>
diff --git a/shared/phone/product.prop b/shared/phone/product.prop
new file mode 100644
index 0000000..2bce15e
--- /dev/null
+++ b/shared/phone/product.prop
@@ -0,0 +1,31 @@
+# Set the Bluetooth Class of Device
+# Service Field: 0x5A -> 90
+#    Bit 17: Networking
+#    Bit 19: Capturing
+#    Bit 20: Object Transfer
+#    Bit 22: Telephony
+# MAJOR_CLASS: 0x02 -> 2 (Phone)
+# MINOR_CLASS: 0x0C -> 12 (Smart Phone)
+bluetooth.device.class_of_device=90,2,12
+
+# Set supported Bluetooth profiles to enabled
+bluetooth.profile.asha.central.enabled=true
+bluetooth.profile.a2dp.source.enabled=true
+bluetooth.profile.avrcp.target.enabled=true
+bluetooth.profile.bap.broadcast.assist.enabled=true
+bluetooth.profile.bap.unicast.client.enabled=true
+bluetooth.profile.bas.client.enabled=true
+bluetooth.profile.csip.set_coordinator.enabled=true
+bluetooth.profile.gatt.enabled=true
+bluetooth.profile.hap.client.enabled=true
+bluetooth.profile.hfp.ag.enabled=true
+bluetooth.profile.hid.device.enabled=true
+bluetooth.profile.hid.host.enabled=true
+bluetooth.profile.map.server.enabled=true
+bluetooth.profile.mcp.server.enabled=true
+bluetooth.profile.opp.enabled=true
+bluetooth.profile.pan.nap.enabled=true
+bluetooth.profile.pan.panu.enabled=true
+bluetooth.profile.pbap.server.enabled=true
+bluetooth.profile.ccp.server.enabled=true
+bluetooth.profile.vcp.controller.enabled=true
diff --git a/shared/sepolicy/OWNERS b/shared/sepolicy/OWNERS
index 2975ddf..9b37b0e 100644
--- a/shared/sepolicy/OWNERS
+++ b/shared/sepolicy/OWNERS
@@ -1,12 +1,4 @@
-adamshih@google.com
+include platform/system/sepolicy:/OWNERS
+
 adelva@google.com
-alanstokes@google.com
-bowgotsai@google.com
-jbires@google.com
-jeffv@google.com
-jgalenson@google.com
-jiyong@google.com
-nnk@google.com
-smoreland@google.com
-sspatil@google.com
-trong@google.com
+rurumihong@google.com
diff --git a/shared/sepolicy/system_ext/private/logger_app.te b/shared/sepolicy/system_ext/private/logger_app.te
deleted file mode 100644
index 33d1934..0000000
--- a/shared/sepolicy/system_ext/private/logger_app.te
+++ /dev/null
@@ -1,17 +0,0 @@
-type logger_app, domain;
-
-# Taken from bonito-sepolicy:
-# https://cs.android.com/android/_/android/device/google/bonito-sepolicy/+/5396ef0aa04dc69ed04ecbc7f55eacf2a76b040b:vendor/qcom/common/logger_app.te;drc=dd2c2053296b0c00b5ef103adcabb8cd82eb0045
-userdebug_or_eng(`
-  app_domain(logger_app)
-  net_domain(logger_app)
-
-  allow logger_app app_api_service:service_manager find;
-  allow logger_app surfaceflinger_service:service_manager find;
-  allow logger_app radio_vendor_data_file:file create_file_perms;
-  allow logger_app radio_vendor_data_file:file rw_file_perms;
-  allow logger_app radio_vendor_data_file:dir create_dir_perms;
-  allow logger_app radio_vendor_data_file:dir rw_dir_perms;
-
-  gpu_access(logger_app)
-')
diff --git a/shared/sepolicy/system_ext/private/sample_tuner_tis_app.te b/shared/sepolicy/system_ext/private/sample_tuner_tis_app.te
deleted file mode 100644
index 60f5e69..0000000
--- a/shared/sepolicy/system_ext/private/sample_tuner_tis_app.te
+++ /dev/null
@@ -1,7 +0,0 @@
-# Sample Tuner TIS app
-type sample_tuner_tis_app, domain;
-
-app_domain(sample_tuner_tis_app)
-
-allow sample_tuner_tis_app app_api_service:service_manager find;
-hal_client_domain(sample_tuner_tis_app, hal_tv_tuner);
diff --git a/shared/sepolicy/system_ext/private/seapp_contexts b/shared/sepolicy/system_ext/private/seapp_contexts
index 1cd8665..95768d0 100644
--- a/shared/sepolicy/system_ext/private/seapp_contexts
+++ b/shared/sepolicy/system_ext/private/seapp_contexts
@@ -1,9 +1,5 @@
 # Ramdump app
 user=_app seinfo=platform name=com.android.ramdump domain=ramdump_app type=app_data_file levelFrom=all
-user=_app seinfo=platform name=com.android.pixellogger domain=logger_app type=app_data_file levelFrom=all
 
 # Connectivity monitor
 user=_app isPrivApp=true seinfo=platform name=com.google.android.connectivitymonitor domain=con_monitor_app type=app_data_file levelFrom=all
-
-# Sample Tuner TIS
-user=system isPrivApp=true seinfo=platform name=com.android.tv.samples.sampletunertvinput domain=sample_tuner_tis_app type=app_data_file levelFrom=all
diff --git a/shared/sepolicy/vendor/adbd.te b/shared/sepolicy/vendor/adbd.te
index d932066..c933fe7 100644
--- a/shared/sepolicy/vendor/adbd.te
+++ b/shared/sepolicy/vendor/adbd.te
@@ -1,2 +1,4 @@
 allow adbd self:{ socket vsock_socket } {create listen accept rw_socket_perms_no_ioctl};
 allow adbd kernel:system module_request;
+
+gpu_access(adbd)
diff --git a/shared/sepolicy/vendor/bluetooth.te b/shared/sepolicy/vendor/bluetooth.te
new file mode 100644
index 0000000..aa2671a
--- /dev/null
+++ b/shared/sepolicy/vendor/bluetooth.te
@@ -0,0 +1 @@
+gpu_access(bluetooth)
diff --git a/shared/sepolicy/vendor/bootanim.te b/shared/sepolicy/vendor/bootanim.te
index 9ac7954..b183efa 100644
--- a/shared/sepolicy/vendor/bootanim.te
+++ b/shared/sepolicy/vendor/bootanim.te
@@ -1,6 +1,2 @@
-# TODO(b/65049764): Update this once the FD owner process is relabelled. This is probably one of the
-# processes whch is started before Android init.
-allow bootanim kernel:fd use;
-
 allow bootanim self:process execmem;
 gpu_access(bootanim)
diff --git a/shared/sepolicy/vendor/bug_map b/shared/sepolicy/vendor/bug_map
index 57cbf3c..fe3d21d 100644
--- a/shared/sepolicy/vendor/bug_map
+++ b/shared/sepolicy/vendor/bug_map
@@ -1,6 +1,8 @@
+init init capability b/199386018
+init logcat_exec file b/216584034
 init system_lib_file dir b/133444385
 init system_lib_file file b/133444385
+kernel kernel capability b/179966921
 migrate_legacy_obb_data dalvikcache_data_file file b/152338071
-system_server system_server process b/65201432
 gmscore_app hal_camera_prop file b/156287758
 priv_app radio_vendor_data_file dir b/188833462
diff --git a/shared/sepolicy/vendor/cuttlefish_sensor_injection.te b/shared/sepolicy/vendor/cuttlefish_sensor_injection.te
index 9e7aca5..83f31f60 100644
--- a/shared/sepolicy/vendor/cuttlefish_sensor_injection.te
+++ b/shared/sepolicy/vendor/cuttlefish_sensor_injection.te
@@ -13,3 +13,4 @@
 
 # Grant cuttlefish_sensor_injection access to the ISensors HAL.
 hal_client_domain(cuttlefish_sensor_injection, hal_sensors)
+binder_use(cuttlefish_sensor_injection)
diff --git a/shared/sepolicy/vendor/dlkm_loader.te b/shared/sepolicy/vendor/dlkm_loader.te
new file mode 100644
index 0000000..c47e229
--- /dev/null
+++ b/shared/sepolicy/vendor/dlkm_loader.te
@@ -0,0 +1,17 @@
+type dlkm_loader, domain;
+type dlkm_loader_exec, exec_type, vendor_file_type, file_type;
+
+init_daemon_domain(dlkm_loader)
+
+# Allow insmod on vendor, system and system_dlkm partitions
+allow dlkm_loader self:capability sys_module;
+allow dlkm_loader system_file:system module_load;
+allow dlkm_loader system_dlkm_file:system module_load;
+allow dlkm_loader vendor_file:system module_load;
+
+# needed for libmodprobe to read kernel commandline
+allow dlkm_loader proc_cmdline:file r_file_perms;
+
+# dlkm_loader searches tracefs while looking for modules
+dontaudit dlkm_loader debugfs_bootreceiver_tracing:dir search;
+dontaudit dlkm_loader debugfs_mm_events_tracing:dir search;
diff --git a/shared/sepolicy/vendor/dumpstate.te b/shared/sepolicy/vendor/dumpstate.te
index 6233ca1..2e5483d 100644
--- a/shared/sepolicy/vendor/dumpstate.te
+++ b/shared/sepolicy/vendor/dumpstate.te
@@ -2,4 +2,6 @@
 
 allow dumpstate system_data_file:dir w_dir_perms;
 
-allow dumpstate gpu_device:dir search;
\ No newline at end of file
+allow dumpstate gpu_device:dir search;
+
+allow dumpstate hal_sensors_default:binder call;
diff --git a/shared/sepolicy/vendor/file.te b/shared/sepolicy/vendor/file.te
index 53c4628..57a3eb0 100644
--- a/shared/sepolicy/vendor/file.te
+++ b/shared/sepolicy/vendor/file.te
@@ -1,7 +1,4 @@
 # File types
 type sensors_hal_socket, file_type;
-type tombstone_snapshot_file, file_type;
-type var_run_system_file, file_type;
-type sysfs_gpu, fs_type, sysfs_type;
 type sysfs_iio_devices, fs_type, sysfs_type;
 type mediadrm_vendor_data_file, file_type, data_file_type;
diff --git a/shared/sepolicy/vendor/file_contexts b/shared/sepolicy/vendor/file_contexts
index 49ef362..01dcc97 100644
--- a/shared/sepolicy/vendor/file_contexts
+++ b/shared/sepolicy/vendor/file_contexts
@@ -4,6 +4,7 @@
 
 /dev/block/by-name/misc u:object_r:misc_block_device:s0
 /dev/block/by-name/boot_[ab] u:object_r:boot_block_device:s0
+/dev/block/by-name/init_boot_[ab] u:object_r:boot_block_device:s0
 /dev/block/by-name/vendor_boot_[ab] u:object_r:boot_block_device:s0
 /dev/block/by-name/vbmeta_[ab] u:object_r:ab_block_device:s0
 /dev/block/by-name/vbmeta_system_[ab] u:object_r:ab_block_device:s0
@@ -14,6 +15,7 @@
 /dev/block/by-name/frp  u:object_r:frp_block_device:s0
 
 /dev/block/pmem0  u:object_r:rebootescrow_device:s0
+/dev/block/pmem1  u:object_r:hal_graphics_composer_pmem_device:s0
 /dev/block/zram0  u:object_r:swap_block_device:s0
 /dev/dri u:object_r:gpu_device:s0
 /dev/dri/card0  u:object_r:graphics_device:s0
@@ -27,22 +29,20 @@
 
 /dev/vhci  u:object_r:bt_device:s0
 
+# gnss hal can also read/write from hvc6 and hvc7
+# hvc6 for gnss raw measurement
+# hvc7 for fixed location
+/dev/hvc6  u:object_r:gnss_device:s0
+/dev/hvc7  u:object_r:gnss_device:s0
+
 # ARM serial console device
 /dev/ttyAMA[0-9]*  u:object_r:serial_device:s0
 
 #############################
-# Root files
-/ts_snap\.txt  u:object_r:tombstone_snapshot_file:s0
-
-#############################
 # data files
 /data/vendor/mediadrm(/.*)?  u:object_r:mediadrm_vendor_data_file:s0
 
 #############################
-# var files
-/var/run/system(/.*)?  u:object_r:var_run_system_file:s0
-
-#############################
 # sys files
 # x86
 /sys/devices/pci0000:00/0000:00:[0-9a-fA-F]{2}\.0/virtio[0-9]+/net(/.*)? u:object_r:sysfs_net:s0
@@ -57,6 +57,7 @@
 # Vendor files
 #
 /vendor/bin/cuttlefish_sensor_injection   u:object_r:cuttlefish_sensor_injection_exec:s0
+/vendor/bin/mac80211_create_radios u:object_r:mac80211_create_radios_exec:s0
 /vendor/bin/socket_vsock_proxy  u:object_r:socket_vsock_proxy_exec:s0
 /vendor/bin/vsoc_input_service  u:object_r:vsoc_input_service_exec:s0
 /vendor/bin/rename_netiface  u:object_r:rename_netiface_exec:s0
@@ -66,42 +67,54 @@
 /vendor/bin/hw/android\.hardware\.camera\.provider@2\.7-service-google u:object_r:hal_camera_default_exec:s0
 /vendor/bin/hw/android\.hardware\.camera\.provider@2\.7-service-google-lazy u:object_r:hal_camera_default_exec:s0
 /vendor/bin/hw/android\.hardware\.power\.stats@1\.0-service\.mock  u:object_r:hal_power_stats_default_exec:s0
+/vendor/bin/hw/android\.hardware.audio.service u:object_r:hal_audio_cuttlefish_exec:s0
 /vendor/bin/hw/android\.hardware\.bluetooth@1\.1-service\.remote  u:object_r:hal_bluetooth_remote_exec:s0
 /vendor/bin/hw/android\.hardware\.bluetooth@1\.1-service\.sim  u:object_r:hal_bluetooth_sim_exec:s0
 /vendor/bin/hw/android\.hardware\.contexthub@1\.2-service\.mock  u:object_r:hal_contexthub_default_exec:s0
 /vendor/bin/hw/android\.hardware\.drm@[0-9]+\.[0-9]+-service\.clearkey  u:object_r:hal_drm_clearkey_exec:s0
 /vendor/bin/hw/android\.hardware\.drm@[0-9]+\.[0-9]+-service-lazy\.clearkey  u:object_r:hal_drm_clearkey_exec:s0
 /vendor/bin/hw/android\.hardware\.drm@[0-9]+\.[0-9]+-service\.widevine  u:object_r:hal_drm_widevine_exec:s0
+/vendor/bin/hw/android\.hardware\.drm-service\.widevine  u:object_r:hal_drm_widevine_exec:s0
 /vendor/bin/hw/android\.hardware\.drm@[0-9]+\.[0-9]+-service-lazy\.widevine  u:object_r:hal_drm_widevine_exec:s0
+/vendor/bin/hw/android\.hardware\.graphics\.allocator-V1-service\.minigbm   u:object_r:hal_graphics_allocator_default_exec:s0
 /vendor/bin/hw/android\.hardware\.graphics\.allocator@4\.0-service\.minigbm   u:object_r:hal_graphics_allocator_default_exec:s0
+/vendor/bin/hw/android\.hardware\.graphics\.composer3-service\.ranchu  u:object_r:hal_graphics_composer_default_exec:s0
 /vendor/bin/hw/android\.hardware\.gatekeeper@1\.0-service\.software  u:object_r:hal_gatekeeper_default_exec:s0
+/vendor/bin/hw/android\.hardware\.health-service\.cuttlefish u:object_r:hal_health_default_exec:s0
 /vendor/bin/hw/android\.hardware\.health\.storage-service\.cuttlefish u:object_r:hal_health_storage_default_exec:s0
 /vendor/bin/hw/android\.hardware\.lights-service\.example u:object_r:hal_light_default_exec:s0
 /vendor/bin/hw/android\.hardware\.neuralnetworks@1\.3-service-sample-.*   u:object_r:hal_neuralnetworks_sample_exec:s0
 /vendor/bin/hw/android\.hardware\.neuralnetworks-shim-service-sample   u:object_r:hal_neuralnetworks_sample_exec:s0
 /vendor/bin/hw/android\.hardware\.neuralnetworks-service-sample-.*   u:object_r:hal_neuralnetworks_sample_exec:s0
+/vendor/bin/hw/android\.hardware\.nfc-service\.cuttlefish  u:object_r:hal_nfc_default_exec:s0
 /vendor/bin/hw/android\.hardware\.vibrator@1\.x-service\.example u:object_r:hal_vibrator_default_exec:s0
+/vendor/bin/hw/android\.hardware\.net\.nlinterceptor-service\.default  u:object_r:hal_nlinterceptor_default_exec:s0
 /vendor/bin/setup_wifi  u:object_r:setup_wifi_exec:s0
 /vendor/bin/bt_vhci_forwarder  u:object_r:bt_vhci_forwarder_exec:s0
-
+/vendor/bin/hw/android\.hardware\.sensors-service\.example  u:object_r:hal_sensors_default_exec:s0
 /vendor/bin/hw/android\.hardware\.sensors@2\.1-service\.mock  u:object_r:hal_sensors_default_exec:s0
 /vendor/bin/hw/android\.hardware\.input\.classifier@1\.0-service.default  u:object_r:hal_input_classifier_default_exec:s0
+/vendor/bin/hw/android\.hardware\.input\.processor-service\.example  u:object_r:hal_input_processor_default_exec:s0
 /vendor/bin/hw/android\.hardware\.thermal@2\.0-service\.mock  u:object_r:hal_thermal_default_exec:s0
+/vendor/bin/hw/android\.hardware\.identity-service\.remote  u:object_r:hal_identity_remote_exec:s0
 /vendor/bin/hw/android\.hardware\.security\.keymint-service\.remote  u:object_r:hal_keymint_remote_exec:s0
 /vendor/bin/hw/android\.hardware\.keymaster@4\.1-service.remote  u:object_r:hal_keymaster_remote_exec:s0
 /vendor/bin/hw/android\.hardware\.gatekeeper@1\.0-service.remote  u:object_r:hal_gatekeeper_remote_exec:s0
+/vendor/bin/hw/android\.hardware\.confirmationui@1\.0-service.cuttlefish  u:object_r:hal_confirmationui_cuttlefish_exec:s0
 /vendor/bin/hw/android\.hardware\.oemlock-service.example u:object_r:hal_oemlock_default_exec:s0
 /vendor/bin/hw/android\.hardware\.weaver-service.example u:object_r:hal_weaver_default_exec:s0
 /vendor/bin/hw/android\.hardware\.authsecret@1\.0-service  u:object_r:hal_authsecret_default_exec:s0
 /vendor/bin/hw/android\.hardware\.authsecret-service.example u:object_r:hal_authsecret_default_exec:s0
 /vendor/bin/hw/android\.hardware\.rebootescrow-service\.default  u:object_r:hal_rebootescrow_default_exec:s0
-/vendor/bin/init\.insmod\.sh  u:object_r:init_insmod_sh_exec:s0
+/vendor/bin/dlkm_loader  u:object_r:dlkm_loader_exec:s0
+/vendor/bin/init\.wifi\.sh    u:object_r:init_wifi_sh_exec:s0
 
 /vendor/lib(64)?/libdrm.so  u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/libglapi.so  u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/dri/.* u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/hw/android\.hardware\.graphics\.mapper@4\.0-impl\.minigbm\.so u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/libminigbm_gralloc.so  u:object_r:same_process_hal_file:s0
+/vendor/lib(64)?/libminigbm_gralloc4_utils.so  u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/hw/android\.hardware\.health@2\.0-impl-2\.1-cuttlefish\.so  u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/hw/vulkan.pastel.so  u:object_r:same_process_hal_file:s0
 /vendor/lib(64)?/libcuttlefish_fs.so  u:object_r:same_process_hal_file:s0
diff --git a/shared/sepolicy/vendor/gceservice.te b/shared/sepolicy/vendor/gceservice.te
index b6f84be..57181ec 100644
--- a/shared/sepolicy/vendor/gceservice.te
+++ b/shared/sepolicy/vendor/gceservice.te
@@ -13,12 +13,6 @@
 allow gceservice kmsg_device:chr_file w_file_perms;
 allow gceservice kmsg_device:chr_file getattr;
 
-# Read tombstone snapshot file
-allow gceservice tombstone_snapshot_file:file r_file_perms;
-# List tombstone files
-allow gceservice tombstone_data_file:dir r_dir_perms;
-allow gceservice tombstone_data_file:file getattr;
-
 # Communicate with GCE Metadata Proxy over Unix domain sockets
 # The proxy process uses the default label ("kernel") because it is
 # started before Android init and thus before SELinux rule are applied.
diff --git a/shared/sepolicy/vendor/genfs_contexts b/shared/sepolicy/vendor/genfs_contexts
index 3a18743..542db04 100644
--- a/shared/sepolicy/vendor/genfs_contexts
+++ b/shared/sepolicy/vendor/genfs_contexts
@@ -7,6 +7,7 @@
 genfscon sysfs $1/0000:00:eval($2 + 1, 16, 2).0/virtio`'eval($3 + 1)`'/block u:object_r:sysfs_devices_block:s0 # vdb
 genfscon sysfs $1/0000:00:eval($2 + 2, 16, 2).0/virtio`'eval($3 + 2)`'/block u:object_r:sysfs_devices_block:s0 # vdc
 genfscon sysfs $1/0000:00:eval($2 + 3, 16, 2).0/virtio`'eval($3 + 3)`'/ndbus0 u:object_r:sysfs_devices_block:s0 # pmem0
+genfscon sysfs $1/0000:00:eval($2 + 4, 16, 2).0/virtio`'eval($3 + 3)`'/ndbus0 u:object_r:sysfs_devices_block:s0 # pmem1
 dnl')dnl
 dnl
 dnl # $1 = pci prefix
@@ -29,42 +30,52 @@
 dnl')dnl
 dnl
 # crosvm (x86)
-cf_pci_block_device(/devices/pci0000:00, 0x6, 5)
-cf_pci_gpu_device(/devices/pci0000:00, 0x11)
+cf_pci_block_device(/devices/pci0000:00, 0xb, 10)
+cf_pci_gpu_device(/devices/pci0000:00, 0x2)
 ## find /sys/devices/platform/* -type d -name 'rtc[0-9]' | sed 's,/rtc[0-9],,'
 genfscon sysfs /devices/platform/rtc_cmos/rtc u:object_r:sysfs_rtc:s0
 ## find /sys/devices/platform/* -type d -name 'wakeup[0-9]'
 genfscon sysfs /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/wakeup u:object_r:sysfs_wakeup:s0
+genfscon sysfs /devices/LNXSYSTM:00/LNXPWRBN:00/wakeup u:object_r:sysfs_wakeup:s0
+genfscon sysfs /devices/platform/rtc_cmos/rtc/rtc0/wakeup3 u:object_r:sysfs_wakeup:s0
 cf_rtc_wakeup_alarmtimer(/devices/platform/rtc_cmos, 0, 1)
 ## currently disabled
 #genfscon sysfs /devices/LNXSYSTM:00/GFSH0001:00/wakeup u:object_r:sysfs_wakeup:s0
+#genfscon sysfs /devices/platform/GFSH0001:00/power_supply u:object_r:sysfs_batteryinfo:s0
+#genfscon sysfs /devices/platform/GFSH0001:00/power_supply/ac/wakeup3 u:object_r:sysfs_wakeup:s0
+#genfscon sysfs /devices/platform/GFSH0001:00/power_supply/battery/wakeup4 u:object_r:sysfs_wakeup:s0
+genfscon sysfs /devices/virtual/mac80211_hwsim/hwsim0/net u:object_r:sysfs_net:s0
+genfscon sysfs /devices/virtual/mac80211_hwsim/hwsim1/net u:object_r:sysfs_net:s0
 
 # crosvm (arm64)
-cf_pci_block_device(/devices/platform/10000.pci, 0x6, 4)
-cf_pci_gpu_device(/devices/platform/10000.pci/pci0000:00, 0x11)
+cf_pci_block_device(/devices/platform/10000.pci/pci0000:00, 0xb, 10)
+cf_pci_gpu_device(/devices/platform/10000.pci/pci0000:00, 0x2)
 ## find /sys/devices/platform/* -type d -name 'rtc[0-9]' | sed 's,/rtc[0-9],,'
 genfscon sysfs /devices/platform/2000.rtc/rtc u:object_r:sysfs_rtc:s0
 ## find /sys/devices/platform/* -type d -name 'wakeup[0-9]'
 ## arm64 2000.rtc on crosvm does not currently expose a wakeup node
 
 # qemu (x86)
-cf_pci_block_device(/devices/pci0000:00, 0x7, 5)
+cf_pci_block_device(/devices/pci0000:00, 0xb, 9)
+#cf_pci_gpu_device(/devices/pci0000:00, 0x2) - duplicated with crosvm(x86)
 ## find /sys/devices/platform/* -type d -name 'rtc[0-9]' | sed 's,/rtc[0-9],,'
 genfscon sysfs /devices/pnp0/00:04/rtc u:object_r:sysfs_rtc:s0
 ## find /sys/devices/platform/* -type d -name 'wakeup[0-9][0-9]'
 cf_rtc_wakeup_alarmtimer(/devices/pnp0/00:04, 0, 19)
 
 # qemu (arm64)
-cf_pci_block_device(/devices/platform/4010000000.pcie/pci0000:00, 0x6, 4)
-cf_pci_gpu_device(/devices/platform/4010000000.pcie/pci0000:00, 0x10)
+cf_pci_block_device(/devices/platform/4010000000.pcie/pci0000:00, 0xa, 9)
+cf_pci_gpu_device(/devices/platform/4010000000.pcie/pci0000:00, 0x2)
 ## find /sys/devices/platform/* -type d -name 'rtc[0-9]' | sed 's,/rtc[0-9],,'
 genfscon sysfs /devices/platform/9010000.pl031/rtc u:object_r:sysfs_rtc:s0
 ## find /sys/devices/platform/* -type d -name 'wakeup[0-9]'
 cf_rtc_wakeup_alarmtimer(/devices/platform/9010000.pl031, 0, 0)
 
 # qemu (arm)
-cf_pci_block_device(/devices/platform/3f000000.pcie/pci0000:00, 0x6, 4)
-cf_pci_gpu_device(/devices/platform/3f000000.pcie/pci0000:00, 0xf)
+cf_pci_block_device(/devices/platform/3f000000.pcie/pci0000:00, 0xa, 9)
+cf_pci_gpu_device(/devices/platform/3f000000.pcie/pci0000:00, 0x2)
+genfscon sysfs /devices/platform/rtc-test.1/wakeup/wakeup2 u:object_r:sysfs_wakeup:s0
+genfscon sysfs /devices/platform/rtc-test.2/wakeup/wakeup3 u:object_r:sysfs_wakeup:s0
 
 # common on all platforms / vm managers
 genfscon sysfs /devices/platform/rtc-test.0/rtc u:object_r:sysfs_rtc:s0
diff --git a/shared/sepolicy/vendor/hal_audio_cuttlefish.te b/shared/sepolicy/vendor/hal_audio_cuttlefish.te
new file mode 100644
index 0000000..0bdd256
--- /dev/null
+++ b/shared/sepolicy/vendor/hal_audio_cuttlefish.te
@@ -0,0 +1,9 @@
+type hal_audio_cuttlefish, domain;
+type hal_audio_cuttlefish_exec, exec_type, vendor_file_type, file_type;
+
+hal_server_domain(hal_audio_cuttlefish, hal_audio)
+
+init_daemon_domain(hal_audio_cuttlefish)
+
+binder_use(hal_audio_cuttlefish)
+allow hal_audio_cuttlefish audioserver:fifo_file write;
diff --git a/shared/sepolicy/vendor/hal_camera_default.te b/shared/sepolicy/vendor/hal_camera_default.te
index e4dac76..e4a1156 100644
--- a/shared/sepolicy/vendor/hal_camera_default.te
+++ b/shared/sepolicy/vendor/hal_camera_default.te
@@ -13,3 +13,8 @@
 
 # Vsocket camera
 allow hal_camera_default self:vsock_socket { accept bind create getopt listen read write };
+
+# The camera HAL can respond to APEX updates (see ApexUpdateListener), but this
+# is not used by the emulated camera HAL APEX. Ignore these denials.
+dontaudit hal_camera_default property_socket:sock_file { write };
+dontaudit hal_camera_default apex_info_file:file { read };
diff --git a/shared/sepolicy/vendor/hal_confirmationui_cuttlefish.te b/shared/sepolicy/vendor/hal_confirmationui_cuttlefish.te
new file mode 100644
index 0000000..13cd1a9
--- /dev/null
+++ b/shared/sepolicy/vendor/hal_confirmationui_cuttlefish.te
@@ -0,0 +1,14 @@
+type hal_confirmationui_cuttlefish, domain;
+hal_server_domain(hal_confirmationui_cuttlefish, hal_confirmationui)
+
+type hal_confirmationui_cuttlefish_exec, exec_type, vendor_file_type, file_type;
+init_daemon_domain(hal_confirmationui_cuttlefish)
+
+vendor_internal_prop(vendor_vsock_confirmationui_port_prop)
+get_prop(hal_confirmationui_cuttlefish, vendor_vsock_confirmationui_port_prop)
+
+allow hal_confirmationui_cuttlefish self:{ vsock_socket } { create getopt read write getattr connect shutdown };
+
+# Write to kernel log (/dev/kmsg)
+allow hal_confirmationui_cuttlefish kmsg_device:chr_file w_file_perms;
+allow hal_confirmationui_cuttlefish kmsg_device:chr_file getattr;
diff --git a/shared/sepolicy/vendor/hal_graphics_composer.te b/shared/sepolicy/vendor/hal_graphics_composer.te
index 4929038..d08af30 100644
--- a/shared/sepolicy/vendor/hal_graphics_composer.te
+++ b/shared/sepolicy/vendor/hal_graphics_composer.te
@@ -1,8 +1,11 @@
-vendor_restricted_prop(vendor_vsock_frames_port_prop)
-
 allow hal_graphics_composer_server hal_graphics_allocator_default_tmpfs:file read;
 allow hal_graphics_composer_server self:{ socket vsock_socket } create_socket_perms_no_ioctl;
 gpu_access(hal_graphics_composer_server)
 
-get_prop(hal_graphics_composer_server, vendor_vsock_frames_port_prop)
 get_prop(hal_graphics_composer_server, vendor_cuttlefish_config_server_port_prop)
+get_prop(hal_graphics_composer_server, vendor_hwcomposer_prop)
+
+# Persistent memory for some hwcomposer configuration.
+type hal_graphics_composer_pmem_device, dev_type;
+allow hal_graphics_composer_server hal_graphics_composer_pmem_device:blk_file rw_file_perms;
+allow hal_graphics_composer_server block_device:dir search;
diff --git a/shared/sepolicy/vendor/hal_identity_remote.te b/shared/sepolicy/vendor/hal_identity_remote.te
new file mode 100644
index 0000000..3f89226
--- /dev/null
+++ b/shared/sepolicy/vendor/hal_identity_remote.te
@@ -0,0 +1,5 @@
+type hal_identity_remote, domain;
+hal_server_domain(hal_identity_remote, hal_identity)
+
+type hal_identity_remote_exec, exec_type, vendor_file_type, file_type;
+init_daemon_domain(hal_identity_remote)
diff --git a/shared/sepolicy/vendor/hal_keymint_remote.te b/shared/sepolicy/vendor/hal_keymint_remote.te
index 27f8291..7d5f6d5 100644
--- a/shared/sepolicy/vendor/hal_keymint_remote.te
+++ b/shared/sepolicy/vendor/hal_keymint_remote.te
@@ -10,3 +10,6 @@
 # Write to kernel log (/dev/kmsg)
 allow hal_keymint_remote kmsg_device:chr_file w_file_perms;
 allow hal_keymint_remote kmsg_device:chr_file getattr;
+
+get_prop(hal_keymint_remote, vendor_security_patch_level_prop)
+get_prop(hal_keymint_remote, vendor_boot_security_patch_level_prop)
diff --git a/shared/sepolicy/vendor/hal_nlinterceptor_default.te b/shared/sepolicy/vendor/hal_nlinterceptor_default.te
new file mode 100644
index 0000000..2419db8
--- /dev/null
+++ b/shared/sepolicy/vendor/hal_nlinterceptor_default.te
@@ -0,0 +1,5 @@
+type hal_nlinterceptor_default, domain;
+hal_server_domain(hal_nlinterceptor_default, hal_nlinterceptor)
+
+type hal_nlinterceptor_default_exec, exec_type, vendor_file_type, file_type;
+init_daemon_domain(hal_nlinterceptor_default)
diff --git a/shared/sepolicy/vendor/hal_sensors.te b/shared/sepolicy/vendor/hal_sensors.te
index 27fc9c8..827060f 100644
--- a/shared/sepolicy/vendor/hal_sensors.te
+++ b/shared/sepolicy/vendor/hal_sensors.te
@@ -1 +1,3 @@
-allow hal_sensors_server sensors_hal_socket:sock_file { create setattr };
\ No newline at end of file
+allow hal_sensors_server sensors_hal_socket:sock_file { create setattr };
+
+allow hal_sensors_default system_server:binder call;
diff --git a/shared/sepolicy/vendor/hal_wifi_default.te b/shared/sepolicy/vendor/hal_wifi_default.te
new file mode 100644
index 0000000..e14c6cb
--- /dev/null
+++ b/shared/sepolicy/vendor/hal_wifi_default.te
@@ -0,0 +1,4 @@
+allow hal_wifi_default hal_wifi_default:netlink_route_socket {
+    create bind write read nlmsg_read nlmsg_readpriv };
+allow hal_wifi_default self:capability { sys_module };
+set_prop(hal_wifi_default, vendor_wlan_versions_prop)
diff --git a/shared/sepolicy/vendor/init.te b/shared/sepolicy/vendor/init.te
index 7f362ef..a19eb13 100644
--- a/shared/sepolicy/vendor/init.te
+++ b/shared/sepolicy/vendor/init.te
@@ -12,8 +12,12 @@
 allow init binfmt_miscfs:file w_file_perms;
 allow init proc:dir mounton;
 
-# init relabel vbmeta* symlinks
+# init relabel vbmeta* and boot* symlinks under /dev/block/by-name/.
 allow init ab_block_device:lnk_file relabelto;
+allow init boot_block_device:lnk_file relabelto;
+
+# init needs to tune block device
+allow init sysfs_devices_block:file w_file_perms;
 
 # /mnt/sdcard -> /storage/self/primary symlink is deprecated. Ignore attempts to
 # create it. This denial is fixed in core policy in Android R aosp/943799.
diff --git a/shared/sepolicy/vendor/init_insmod_sh.te b/shared/sepolicy/vendor/init_insmod_sh.te
deleted file mode 100644
index 5400a37..0000000
--- a/shared/sepolicy/vendor/init_insmod_sh.te
+++ /dev/null
@@ -1,11 +0,0 @@
-type init_insmod_sh, domain;
-type init_insmod_sh_exec, exec_type, vendor_file_type, file_type;
-
-init_daemon_domain(init_insmod_sh)
-
-allow init_insmod_sh vendor_shell_exec:file rx_file_perms;
-allow init_insmod_sh vendor_toolbox_exec:file rx_file_perms;
-
-# Allow insmod
-allow init_insmod_sh self:capability sys_module;
-allow init_insmod_sh vendor_file:system module_load;
diff --git a/shared/sepolicy/vendor/init_wifi_sh.te b/shared/sepolicy/vendor/init_wifi_sh.te
new file mode 100644
index 0000000..331a745
--- /dev/null
+++ b/shared/sepolicy/vendor/init_wifi_sh.te
@@ -0,0 +1,14 @@
+# cuttlefish-setup service: runs init.cuttlefish.sh script
+type init_wifi_sh, domain;
+type init_wifi_sh_exec, vendor_file_type, exec_type, file_type;
+
+init_daemon_domain(init_wifi_sh)
+
+allow init_wifi_sh self:capability { fowner chown net_admin net_raw };
+allow init_wifi_sh vendor_toolbox_exec:file execute_no_trans;
+allow init_wifi_sh mac80211_create_radios_exec:file execute_no_trans;
+
+vendor_internal_prop(vendor_wifi_mac_prefix);
+get_prop(init_wifi_sh, vendor_wifi_mac_prefix);
+
+allow init_wifi_sh self:netlink_generic_socket create_socket_perms_no_ioctl;
diff --git a/shared/sepolicy/vendor/libcuttlefish_rild.te b/shared/sepolicy/vendor/libcuttlefish_rild.te
index 8f3bbe7..28412c7 100644
--- a/shared/sepolicy/vendor/libcuttlefish_rild.te
+++ b/shared/sepolicy/vendor/libcuttlefish_rild.te
@@ -11,4 +11,4 @@
 get_prop(libcuttlefish_rild, vendor_cuttlefish_config_server_port_prop)
 get_prop(libcuttlefish_rild, vendor_modem_simulator_ports_prop)
 
-allow libcuttlefish_rild self:{ socket vsock_socket } create_socket_perms_no_ioctl;
+allow libcuttlefish_rild self:{ socket vsock_socket } { create_socket_perms_no_ioctl getattr };
diff --git a/shared/sepolicy/vendor/mac80211_create_radios.te b/shared/sepolicy/vendor/mac80211_create_radios.te
new file mode 100644
index 0000000..f7e6b7f
--- /dev/null
+++ b/shared/sepolicy/vendor/mac80211_create_radios.te
@@ -0,0 +1,2 @@
+type mac80211_create_radios, domain;
+type mac80211_create_radios_exec, exec_type, vendor_file_type, file_type;
diff --git a/shared/sepolicy/system_ext/private/mediatranscoding.te b/shared/sepolicy/vendor/mediatranscoding.te
similarity index 100%
rename from shared/sepolicy/system_ext/private/mediatranscoding.te
rename to shared/sepolicy/vendor/mediatranscoding.te
diff --git a/shared/sepolicy/vendor/platform_app.te b/shared/sepolicy/vendor/platform_app.te
index ba23d18..bb4160d 100644
--- a/shared/sepolicy/vendor/platform_app.te
+++ b/shared/sepolicy/vendor/platform_app.te
@@ -1,3 +1,4 @@
 gpu_access(platform_app)
 
-allow platform_app broadcastradio_service:service_manager find;
\ No newline at end of file
+allow platform_app broadcastradio_service:service_manager find;
+allow platform_app hal_wlc_hwservice:hwservice_manager find;
\ No newline at end of file
diff --git a/shared/sepolicy/vendor/property.te b/shared/sepolicy/vendor/property.te
index a727365..91b30fc 100644
--- a/shared/sepolicy/vendor/property.te
+++ b/shared/sepolicy/vendor/property.te
@@ -1,2 +1,5 @@
 vendor_restricted_prop(vendor_cuttlefish_config_server_port_prop)
 vendor_internal_prop(vendor_modem_simulator_ports_prop)
+vendor_internal_prop(vendor_boot_security_patch_level_prop)
+vendor_internal_prop(vendor_hwcomposer_prop)
+vendor_restricted_prop(vendor_wlan_versions_prop)
diff --git a/shared/sepolicy/vendor/property_contexts b/shared/sepolicy/vendor/property_contexts
index ebbe271..9b98ed1 100644
--- a/shared/sepolicy/vendor/property_contexts
+++ b/shared/sepolicy/vendor/property_contexts
@@ -5,10 +5,16 @@
 ro.boot.hardware.hwcomposer u:object_r:vendor_graphics_config_prop:s0 exact string
 ro.boot.hardware.vulkan u:object_r:vendor_graphics_config_prop:s0 exact string
 ro.boot.lcd_density u:object_r:vendor_graphics_config_prop:s0 exact int
-ro.boot.vsock_frames_port  u:object_r:vendor_vsock_frames_port_prop:s0
 ro.boot.vsock_keyboard_port  u:object_r:vendor_vsock_keyboard_port:s0
+ro.boot.vsock_confirmationui_port  u:object_r:vendor_vsock_confirmationui_port_prop:s0
 ro.boot.modem_simulator_ports  u:object_r:vendor_modem_simulator_ports_prop:s0
 ro.boot.vsock_touch_port  u:object_r:vendor_vsock_touch_port:s0
-ro.boot.wifi_mac_address  u:object_r:vendor_wifi_mac_address:s0
+ro.boot.wifi_mac_prefix  u:object_r:vendor_wifi_mac_prefix:s0 exact string
+ro.vendor.wifi_impl u:object_r:vendor_wifi_impl:s0 exact string
+ro.vendor.boot_security_patch u:object_r:vendor_boot_security_patch_level_prop:s0
 vendor.bt.rootcanal_mac_address  u:object_r:vendor_bt_rootcanal_prop:s0
 vendor.bt.rootcanal_test_console  u:object_r:vendor_bt_rootcanal_prop:s0
+ro.vendor.hwcomposer.mode  u:object_r:vendor_hwcomposer_prop:s0 exact string
+ro.vendor.hwcomposer.pmem  u:object_r:vendor_hwcomposer_prop:s0 exact string
+vendor.wlan.firmware.version   u:object_r:vendor_wlan_versions_prop:s0 exact string
+vendor.wlan.driver.version     u:object_r:vendor_wlan_versions_prop:s0 exact string
diff --git a/shared/sepolicy/vendor/rename_netiface.te b/shared/sepolicy/vendor/rename_netiface.te
index 1ec0f06..f648a7e 100644
--- a/shared/sepolicy/vendor/rename_netiface.te
+++ b/shared/sepolicy/vendor/rename_netiface.te
@@ -5,6 +5,6 @@
 
 allow rename_netiface self:capability { net_admin net_raw sys_module };
 allow rename_netiface self:udp_socket { create ioctl };
-allow rename_netiface self:netlink_route_socket { bind create nlmsg_write read write };
+allow rename_netiface self:netlink_route_socket { bind create nlmsg_write read write getattr };
 
 allow rename_netiface kernel:system module_request;
diff --git a/shared/sepolicy/vendor/service_contexts b/shared/sepolicy/vendor/service_contexts
index d20d026..c41503e 100644
--- a/shared/sepolicy/vendor/service_contexts
+++ b/shared/sepolicy/vendor/service_contexts
@@ -1,3 +1,4 @@
+android.hardware.drm.IDrmFactory/widevine    u:object_r:hal_drm_service:s0
 android.hardware.neuralnetworks.IDevice/nnapi-sample_all u:object_r:hal_neuralnetworks_service:s0
 android.hardware.neuralnetworks.IDevice/nnapi-sample_float_fast u:object_r:hal_neuralnetworks_service:s0
 android.hardware.neuralnetworks.IDevice/nnapi-sample_float_slow u:object_r:hal_neuralnetworks_service:s0
diff --git a/shared/sepolicy/vendor/setup_wifi.te b/shared/sepolicy/vendor/setup_wifi.te
index 23a34eb..6a8d054 100644
--- a/shared/sepolicy/vendor/setup_wifi.te
+++ b/shared/sepolicy/vendor/setup_wifi.te
@@ -5,10 +5,8 @@
 
 allow setup_wifi self:capability { net_admin net_raw sys_module };
 allow setup_wifi self:udp_socket { create ioctl };
-allow setup_wifi self:netlink_route_socket { bind create nlmsg_write read write };
+allow setup_wifi self:netlink_route_socket { bind create nlmsg_write read write getattr };
 
 allow setup_wifi kernel:system module_request;
 
-vendor_internal_prop(vendor_wifi_mac_address)
-
-get_prop(setup_wifi, vendor_wifi_mac_address)
+get_prop(setup_wifi, vendor_wifi_mac_prefix)
diff --git a/shared/sepolicy/vendor/socket_vsock_proxy.te b/shared/sepolicy/vendor/socket_vsock_proxy.te
index d4e2b1a..6f72963 100644
--- a/shared/sepolicy/vendor/socket_vsock_proxy.te
+++ b/shared/sepolicy/vendor/socket_vsock_proxy.te
@@ -4,7 +4,7 @@
 init_daemon_domain(socket_vsock_proxy)
 
 allow socket_vsock_proxy self:global_capability_class_set { net_admin net_raw };
-allow socket_vsock_proxy self:{ socket vsock_socket } { create getopt read write listen accept bind shutdown };
+allow socket_vsock_proxy self:{ socket vsock_socket } { create getopt read write getattr listen accept bind shutdown };
 
 # TODO: socket returned by accept() has unlabeled context on it. Give it a
 # specific label.
diff --git a/shared/sepolicy/vendor/system_server.te b/shared/sepolicy/vendor/system_server.te
index 372ca50..171ea52 100644
--- a/shared/sepolicy/vendor/system_server.te
+++ b/shared/sepolicy/vendor/system_server.te
@@ -1,11 +1,10 @@
-# TODO(b/65201432): Switch into enforcing mode once execmem issue due to OpenGL is resolved. Also
-# remove the corresponding dontaudit.
-# The current (at the time of writing) implementation of OpenGL needs to create executable memory.
-# Unfortunately, we cannot grant execmem power using an allow rule because global policy
-# (system/sepolicy) contains a corresponding neverallow which would cause build-time errors if the
-# allow execmem rule were added here.
-permissive system_server;
 gpu_access(system_server)
 
 # Cuttlefish is still using the legacy wifi HAL (pre-HIDL)
 get_prop(system_server, wifi_hal_prop)
+
+# TODO(b/65201432): Swiftshader needs to create executable memory.
+allow system_server self:process execmem;
+
+# For com.android.tethering.inprocess
+dontaudit system_server { fs_bpf fs_bpf_tethering }:dir search;
diff --git a/shared/sepolicy/vendor/vendor_init.te b/shared/sepolicy/vendor/vendor_init.te
index 37e76e1..6a01641 100644
--- a/shared/sepolicy/vendor/vendor_init.te
+++ b/shared/sepolicy/vendor/vendor_init.te
@@ -5,5 +5,11 @@
 }:chr_file { getattr };
 
 set_prop(vendor_init, vendor_bt_rootcanal_prop)
+set_prop(vendor_init, vendor_hwcomposer_prop)
 
 get_prop(vendor_init, vendor_graphics_config_prop)
+
+vendor_internal_prop(vendor_wifi_impl)
+set_prop(vendor_init, vendor_wifi_impl)
+
+set_prop(vendor_init, vendor_boot_security_patch_level_prop)
diff --git a/shared/slim/Android.bp b/shared/slim/Android.bp
new file mode 100644
index 0000000..2d5cac7
--- /dev/null
+++ b/shared/slim/Android.bp
@@ -0,0 +1,10 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+    name: "slim_excluded_hardware.prebuilt.xml",
+    src: "slim_excluded_hardware.xml",
+    relative_install_path: "permissions",
+    soc_specific: true,
+}
diff --git a/shared/slim/android-info.txt b/shared/slim/android-info.txt
new file mode 100644
index 0000000..b0bf39b
--- /dev/null
+++ b/shared/slim/android-info.txt
@@ -0,0 +1 @@
+config=slim
diff --git a/shared/slim/device_vendor.mk b/shared/slim/device_vendor.mk
new file mode 100644
index 0000000..cc46e5c
--- /dev/null
+++ b/shared/slim/device_vendor.mk
@@ -0,0 +1,73 @@
+#
+# 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.
+#
+
+PRODUCT_MANIFEST_FILES += device/google/cuttlefish/shared/config/product_manifest.xml
+SYSTEM_EXT_MANIFEST_FILES += device/google/cuttlefish/shared/config/system_ext_manifest.xml
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_vendor.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_vendor.mk)
+
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/handheld_core_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/handheld_core_hardware.xml
+PRODUCT_PACKAGES += slim_excluded_hardware.prebuilt.xml
+endif
+
+$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
+$(call inherit-product, device/google/cuttlefish/shared/device.mk)
+
+PRODUCT_VENDOR_PROPERTIES += \
+    keyguard.no_require_sim=true \
+    ro.cdma.home.operator.alpha=Android \
+    ro.cdma.home.operator.numeric=302780 \
+    ro.com.android.dataroaming=true \
+    ro.telephony.default_network=9 \
+
+TARGET_USES_CF_RILD ?= true
+ifeq ($(TARGET_USES_CF_RILD),true)
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.cf.rild
+else
+PRODUCT_PACKAGES += \
+    libcuttlefish-ril-2 \
+    libcuttlefish-rild
+endif
+endif
+
+PRODUCT_VENDOR_PROPERTIES += \
+    debug.hwui.drawing_enabled=0 \
+
+ifneq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/android.hardware.biometrics.face.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.biometrics.face.xml \
+    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 \
+    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
+endif
+
+# 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/slim/slim_excluded_hardware.xml b/shared/slim/slim_excluded_hardware.xml
new file mode 100644
index 0000000..a5263e2
--- /dev/null
+++ b/shared/slim/slim_excluded_hardware.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<permissions>
+    <unavailable-feature name="android.software.secure_lock_screen" />
+    <unavailable-feature name="android.software.picture_in_picture" />
+</permissions>
diff --git a/shared/tv/OWNERS b/shared/tv/OWNERS
new file mode 100644
index 0000000..4df9f27
--- /dev/null
+++ b/shared/tv/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 760438
+include device/google/atv:/OWNERS
diff --git a/shared/tv/device.mk b/shared/tv/device_vendor.mk
similarity index 70%
rename from shared/tv/device.mk
rename to shared/tv/device_vendor.mk
index 9b78ee1..5b91b64 100644
--- a/shared/tv/device.mk
+++ b/shared/tv/device_vendor.mk
@@ -14,22 +14,25 @@
 # limitations under the License.
 #
 
-DEVICE_MANIFEST_FILE += device/google/cuttlefish/shared/tv/manifest.xml
+PRODUCT_MANIFEST_FILES += device/google/cuttlefish/shared/config/product_manifest.xml
+SYSTEM_EXT_MANIFEST_FILES += device/google/cuttlefish/shared/config/system_ext_manifest.xml
 
-$(call inherit-product, $(SRC_TARGET_DIR)/product/core_minimal.mk)
+$(call inherit-product, device/google/atv/products/atv_vendor.mk)
 $(call inherit-product, device/google/cuttlefish/shared/device.mk)
+$(call inherit-product, frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk)
 
 # Extend cuttlefish common sepolicy with tv-specific functionality
 BOARD_SEPOLICY_DIRS += device/google/cuttlefish/shared/tv/sepolicy/vendor
 
 PRODUCT_COPY_FILES += \
-    device/google/atv/permissions/tv_core_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/tv_core_hardware.xml \
     frameworks/native/data/etc/android.hardware.bluetooth.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.bluetooth.xml \
     frameworks/native/data/etc/android.hardware.hdmi.cec.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.hdmi.cec.xml \
     frameworks/native/data/etc/android.hardware.sensor.accelerometer.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.accelerometer.xml \
     frameworks/native/data/etc/android.hardware.sensor.compass.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.compass.xml \
+    frameworks/native/data/etc/android.hardware.tv.tuner.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.tv.tuner.xml \
     hardware/interfaces/tv/tuner/config/sample_tuner_vts_config_1_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/tuner_vts_config_1_0.xml \
     hardware/interfaces/tv/tuner/config/sample_tuner_vts_config_1_1.xml:$(TARGET_COPY_OUT_VENDOR)/etc/tuner_vts_config_1_1.xml \
+    hardware/interfaces/tv/tuner/config/sample_tuner_vts_config_aidl_V1.xml:$(TARGET_COPY_OUT_VENDOR)/etc/tuner_vts_config_aidl_V1.xml
 
 # HDMI CEC HAL
 PRODUCT_PACKAGES += android.hardware.tv.cec@1.1-service
@@ -38,7 +41,13 @@
 PRODUCT_PROPERTY_OVERRIDES += ro.hdmi.device_type=4
 
 # Tuner HAL
-PRODUCT_PACKAGES += android.hardware.tv.tuner@1.1-service
+PRODUCT_PACKAGES += android.hardware.tv.tuner-service.example
+
+# Sample Tuner Input for testing
+#PRODUCT_PACKAGES += LiveTv sampletunertvinput
+
+# Fallback IME and Home apps
+PRODUCT_PACKAGES += LeanbackIME TvSampleLeanbackLauncher TvProvision
 
 # Enabling managed profiles
 DEVICE_PACKAGE_OVERLAYS += device/google/cuttlefish/shared/tv/overlay
diff --git a/shared/tv/overlay/frameworks/base/core/res/res/values/config.xml b/shared/tv/overlay/frameworks/base/core/res/res/values/config.xml
index 46c64b7..195a8a7 100644
--- a/shared/tv/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/shared/tv/overlay/frameworks/base/core/res/res/values/config.xml
@@ -17,8 +17,4 @@
 <resources>
     <!--  Maximum number of supported users -->
     <integer name="config_multiuserMaximumUsers">4</integer>
-    <!-- Restricting eth1 -->
-    <string-array translatable="false" name="config_ethernet_interfaces">
-        <item>eth1;11,12,14;;</item>
-    </string-array>
 </resources>
diff --git a/shared/wear/android-info.txt b/shared/wear/android-info.txt
new file mode 100644
index 0000000..22a5b5e
--- /dev/null
+++ b/shared/wear/android-info.txt
@@ -0,0 +1 @@
+config=wear
diff --git a/shared/wear/aosp_product.mk b/shared/wear/aosp_product.mk
new file mode 100644
index 0000000..ce6942e
--- /dev/null
+++ b/shared/wear/aosp_product.mk
@@ -0,0 +1,35 @@
+#
+# 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.
+#
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/base_product.mk)
+
+# Default AOSP sounds
+$(call inherit-product-if-exists, frameworks/base/data/sounds/AllAudio.mk)
+
+# Wear pulls in some obsolete samples as well
+_ringtones := Callisto Dione Ganymede Luna Oberon Phobos Sedna Triton Umbriel
+PRODUCT_COPY_FILES += \
+    frameworks/base/data/sounds/alarms/ogg/Oxygen.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Oxygen.ogg \
+    frameworks/base/data/sounds/notifications/ogg/Tethys.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Tethys.ogg \
+    $(call product-copy-files-by-pattern,frameworks/base/data/sounds/ringtones/ogg/%.ogg,$(TARGET_COPY_OUT_PRODUCT)/media/audio/ringtones/%.ogg,$(_ringtones)) \
+
+PRODUCT_PRODUCT_PROPERTIES += \
+    ro.config.alarm_alert=Oxygen.ogg \
+    ro.config.notification_sound=Tethys.ogg \
+    ro.config.ringtone=Atria.ogg \
+
+PRODUCT_COPY_FILES += \
+    device/sample/etc/apns-full-conf.xml:$(TARGET_COPY_OUT_PRODUCT)/etc/apns-conf.xml \
diff --git a/shared/wear/aosp_system.mk b/shared/wear/aosp_system.mk
new file mode 100644
index 0000000..7e8f75e
--- /dev/null
+++ b/shared/wear/aosp_system.mk
@@ -0,0 +1,78 @@
+#
+# 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.
+#
+
+OVERRIDE_TARGET_FLATTEN_APEX := true
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/base_system.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/languages_default.mk)
+$(call inherit-product-if-exists, external/hyphenation-patterns/patterns.mk)
+$(call inherit-product-if-exists, external/noto-fonts/fonts.mk)
+$(call inherit-product-if-exists, external/roboto-fonts/fonts.mk)
+$(call inherit-product-if-exists, frameworks/base/data/keyboards/keyboards.mk)
+$(call inherit-product-if-exists, frameworks/base/data/fonts/fonts.mk)
+$(call inherit-product-if-exists, vendor/google/security/adb/vendor_key.mk)
+
+PRODUCT_PACKAGES += \
+    BlockedNumberProvider \
+    Bluetooth \
+    CalendarProvider \
+    CertInstaller \
+    clatd \
+    clatd.conf \
+    DownloadProvider \
+    ethernet-service \
+    fsck.f2fs \
+    FusedLocation \
+    InputDevices \
+    KeyChain \
+    librs_jni \
+    ManagedProvisioning \
+    MmsService \
+    netutils-wrapper-1.0 \
+    screenrecord \
+    StatementService \
+    TelephonyProvider \
+    TeleService \
+    UserDictionaryProvider \
+
+PRODUCT_HOST_PACKAGES += \
+    fsck.f2fs \
+
+PRODUCT_SYSTEM_SERVER_APPS += \
+    FusedLocation \
+    InputDevices \
+    KeyChain \
+    Telecom \
+
+PRODUCT_SYSTEM_SERVER_JARS += \
+    services \
+    ethernet-service \
+
+PRODUCT_COPY_FILES += \
+    system/core/rootdir/etc/public.libraries.wear.txt:system/etc/public.libraries.txt \
+    system/core/rootdir/init.zygote32.rc:system/etc/init/hw/init.zygote32.rc \
+
+PRODUCT_USE_DYNAMIC_PARTITION_SIZE := true
+
+PRODUCT_ENFORCE_RRO_TARGETS := *
+
+PRODUCT_BRAND := generic
+
+PRODUCT_SYSTEM_NAME := mainline
+PRODUCT_SYSTEM_BRAND := Android
+PRODUCT_SYSTEM_MANUFACTURER := Android
+PRODUCT_SYSTEM_MODEL := mainline
+PRODUCT_SYSTEM_DEVICE := generic
diff --git a/vsoc_x86_noapex/BoardConfig.mk b/shared/wear/aosp_system_ext.mk
similarity index 73%
rename from vsoc_x86_noapex/BoardConfig.mk
rename to shared/wear/aosp_system_ext.mk
index 934b3e9..2fade9a 100644
--- a/vsoc_x86_noapex/BoardConfig.mk
+++ b/shared/wear/aosp_system_ext.mk
@@ -1,5 +1,5 @@
 #
-# Copyright 2019 The Android Open-Source Project
+# 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.
@@ -14,10 +14,6 @@
 # limitations under the License.
 #
 
-#
-# x86 target for Cuttlefish that doesn't support APEX.
-#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/base_system_ext.mk)
 
-include device/google/cuttlefish/vsoc_x86/BoardConfig.mk
-
-TARGET_FLATTEN_APEX := true
+PRODUCT_PACKAGES += CarrierConfig
diff --git a/shared/wear/aosp_vendor.mk b/shared/wear/aosp_vendor.mk
new file mode 100644
index 0000000..18727f6
--- /dev/null
+++ b/shared/wear/aosp_vendor.mk
@@ -0,0 +1,43 @@
+#
+# 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.
+#
+
+PRODUCT_VENDOR_PROPERTIES += \
+    ro.config.low_ram=true \
+
+PRODUCT_SYSTEM_SERVER_COMPILER_FILTER := speed-profile
+
+PRODUCT_ALWAYS_PREOPT_EXTRACTED_APK := true
+
+PRODUCT_USE_PROFILE_FOR_BOOT_IMAGE := true
+PRODUCT_DEX_PREOPT_BOOT_IMAGE_PROFILE_LOCATION := frameworks/base/config/boot-image-profile.txt
+
+PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD := false
+
+PRODUCT_PACKAGES += \
+    CellBroadcastAppPlatform \
+    CellBroadcastServiceModulePlatform \
+    com.android.tethering.inprocess \
+    InProcessNetworkStack \
+
+PRODUCT_MINIMIZE_JAVA_DEBUG_INFO := true
+
+ifneq (,$(filter eng, $(TARGET_BUILD_VARIANT)))
+    PRODUCT_DISABLE_SCUDO := true
+endif
+
+TARGET_SYSTEM_PROP += device/google/cuttlefish/shared/wear/wearable-1024.prop
+
+TARGET_VNDK_USE_CORE_VARIANT := true
diff --git a/shared/wear/device_vendor.mk b/shared/wear/device_vendor.mk
new file mode 100644
index 0000000..b37c775
--- /dev/null
+++ b/shared/wear/device_vendor.mk
@@ -0,0 +1,60 @@
+#
+# 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.
+#
+
+PRODUCT_MANIFEST_FILES += device/google/cuttlefish/shared/config/product_manifest.xml
+SYSTEM_EXT_MANIFEST_FILES += device/google/cuttlefish/shared/config/system_ext_manifest.xml
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_vendor.mk)
+
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/android.software.backup.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.backup.xml \
+    frameworks/native/data/etc/android.software.connectionservice.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.connectionservice.xml \
+    frameworks/native/data/etc/android.software.device_admin.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.device_admin.xml \
+    frameworks/native/data/etc/wearable_core_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/wearable_core_hardware.xml \
+
+$(call inherit-product, device/google/cuttlefish/shared/device.mk)
+
+PRODUCT_VENDOR_PROPERTIES += \
+    keyguard.no_require_sim=true \
+    ro.cdma.home.operator.alpha=Android \
+    ro.cdma.home.operator.numeric=302780 \
+    ro.com.android.dataroaming=true \
+    ro.telephony.default_network=9 \
+
+TARGET_USES_CF_RILD ?= true
+ifeq ($(TARGET_USES_CF_RILD),true)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_vendor.mk)
+PRODUCT_PACKAGES += \
+    libcuttlefish-ril-2 \
+    libcuttlefish-rild
+endif
+
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/android.hardware.audio.output.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.audio.output.xml \
+    frameworks/native/data/etc/android.hardware.faketouch.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.faketouch.xml \
+    frameworks/native/data/etc/android.hardware.location.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.location.xml \
+    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 \
+
+# Runtime Resource Overlays
+PRODUCT_PACKAGES += \
+    cuttlefish_phone_overlay_frameworks_base_core \
+    cuttlefish_wear_overlay_frameworks_base_core \
+    cuttlefish_wear_overlay_settings_provider \
+
+PRODUCT_CHARACTERISTICS := nosdcard,watch
+
+TARGET_BOARD_INFO_FILE ?= device/google/cuttlefish/shared/wear/android-info.txt
diff --git a/shared/wear/overlays/SettingsProvider/Android.bp b/shared/wear/overlays/SettingsProvider/Android.bp
new file mode 100644
index 0000000..e6bde39
--- /dev/null
+++ b/shared/wear/overlays/SettingsProvider/Android.bp
@@ -0,0 +1,8 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+    name: "cuttlefish_wear_overlay_settings_provider",
+    soc_specific: true,
+}
diff --git a/shared/wear/overlays/SettingsProvider/AndroidManifest.xml b/shared/wear/overlays/SettingsProvider/AndroidManifest.xml
new file mode 100644
index 0000000..273d67b
--- /dev/null
+++ b/shared/wear/overlays/SettingsProvider/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.providers.settings.cuttlefish.wear.overlay">
+
+    <application android:hasCode="false" />
+
+    <overlay
+      android:targetPackage="com.android.providers.settings"
+      android:isStatic="true"
+      android:priority="3"
+    />
+</manifest>
diff --git a/shared/wear/overlays/SettingsProvider/res/values/defaults.xml b/shared/wear/overlays/SettingsProvider/res/values/defaults.xml
new file mode 100644
index 0000000..b02a9af
--- /dev/null
+++ b/shared/wear/overlays/SettingsProvider/res/values/defaults.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, The Android Open Source Project.
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+    <bool name="def_accelerometer_rotation">false</bool>
+
+    <integer name="def_screen_off_timeout">15000</integer>
+    <bool name="def_lockscreen_disabled">false</bool>
+    <bool name="def_sound_effects_enabled">false</bool>
+    <integer name="def_power_sounds_enabled">0</integer>
+    <integer name="def_lockscreen_sounds_enabled">0</integer>
+    <integer name="def_dock_sounds_enabled">0</integer>
+    <integer name="def_dock_sounds_enabled_when_accessibility">1</integer>
+    <string name="def_location_providers_allowed" translatable="false">gps,network</string>
+    <string name="def_bluetooth_disabled_profiles">65536</string>
+    <bool name="def_accessibility_speak_password">true</bool>
+    <bool name="def_auto_time">false</bool>
+    <bool name="def_auto_time_zone">false</bool>
+    <bool name="def_mobile_data_always_on">false</bool>
+    <bool name="def_wifi_on">true</bool>
+    <bool name="def_wifi_wakeup_enabled">false</bool>
+    <bool name="def_vibrate_when_ringing">true</bool>
+</resources>
diff --git a/shared/wear/overlays/core/Android.bp b/shared/wear/overlays/core/Android.bp
new file mode 100644
index 0000000..2f522a9
--- /dev/null
+++ b/shared/wear/overlays/core/Android.bp
@@ -0,0 +1,8 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+runtime_resource_overlay {
+    name: "cuttlefish_wear_overlay_frameworks_base_core",
+    soc_specific: true,
+}
diff --git a/shared/wear/overlays/core/AndroidManifest.xml b/shared/wear/overlays/core/AndroidManifest.xml
new file mode 100644
index 0000000..c4ae538
--- /dev/null
+++ b/shared/wear/overlays/core/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cuttlefish.wear.overlay">
+
+    <application android:hasCode="false" />
+
+    <overlay
+      android:targetPackage="android"
+      android:isStatic="true"
+      android:priority="3"
+      />
+</manifest>
diff --git a/shared/wear/overlays/core/res/values/config.xml b/shared/wear/overlays/core/res/values/config.xml
new file mode 100644
index 0000000..a07169f
--- /dev/null
+++ b/shared/wear/overlays/core/res/values/config.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, The Android Open Source Project.
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="networkAttributes" translatable="false">
+    <item>"mobile,0,0,0,-1,true"</item>
+    <item>"wifi,1,1,1,-1,true"</item>
+    <item>"mobile_mms,2,0,2,60000,true"</item>
+    <item>"mobile_hipri,5,0,3,60000,true"</item>
+    <item>"bluetooth,7,7,2,-1,true"</item>
+    <item>"proxy,16,16,1,-1,true"</item>
+  </string-array>
+  <string-array name="radioAttributes" translatable="false">
+    <item>"0,1"</item>
+    <item>"1,1"</item>
+    <item>"4,1"</item>
+    <item>"7,1"</item>
+    <item>"11,1"</item>
+    <item>"16,1"</item>
+  </string-array>
+  <bool name="config_sms_capable">false</bool>
+  <bool name="config_showNavigationBar" translatable="false">false</bool>
+
+  <integer name="config_networkTransitionTimeout">0</integer>
+  <bool name="config_voice_capable">true</bool>
+  <bool name="config_requireCallCapableAccountForHandle">true</bool>
+  <bool name="config_enableWallpaperService">true</bool>
+  <bool name="config_dreamsSupported">false</bool>
+  <!--<bool name="config_suspendWhenScreenOffDueToProximity">true</bool>-->
+  <!--<bool name="config_powerDecoupleAutoSuspendModeFromDisplay">true</bool>-->
+  <!--<bool name="config_powerDecoupleInteractiveModeFromDisplay">true</bool>-->
+  <bool name="config_allowTheaterModeWakeFromPowerKey">true</bool>
+  <bool name="config_allowTheaterModeWakeFromKey">true</bool>
+  <bool name="config_supportAutoRotation">true</bool>
+  <bool name="config_hasRecents">false</bool>
+  <integer name="config_minimumScreenOffTimeout">5000</integer>
+  <integer name="config_maximumScreenDimDuration">300</integer>
+  <fraction name="config_maximumScreenDimRatio">20%</fraction>
+  <integer name="config_notificationServiceArchiveSize">1</integer>
+  <bool name="config_networkSamplingWakesDevice">false</bool>
+  <bool name="config_goToSleepOnButtonPressTheaterMode">false</bool>
+  <integer name="config_triplePressOnPowerBehavior">0</integer>
+  <integer name="config_shortPressOnPowerBehavior">5</integer>
+  <bool name="config_supportLongPressPowerWhenNonInteractive">true</bool>
+  <integer name="config_longPressOnPowerBehavior">0</integer>
+  <integer name="config_veryLongPressOnPowerBehavior">1</integer>
+  <bool name="config_allowStartActivityForLongPressOnPowerInSetup">true</bool>
+  <bool name="config_enableNetworkLocationOverlay" translatable="false">false</bool>
+  <bool name="config_allowAnimationsInLowPowerMode">true</bool>
+  <bool name="config_enableAutoPowerModes">true</bool>
+  <bool name="config_autoPowerModePreferWristTilt">true</bool>
+  <bool name="config_autoPowerModePrefetchLocation">false</bool>
+  <integer name="config_autoPowerModeThresholdAngle">10</integer>
+  <integer name="config_lowMemoryKillerMinFreeKbytesAbsolute">65536</integer>
+  <integer name="config_extraFreeKbytesAbsolute">14100</integer>
+  <integer name="config_previousVibrationsDumpLimit">100</integer>
+  <bool name="config_allowPriorityVibrationsInLowPowerMode">true</bool>
+  <bool name="config_cameraDoubleTapPowerGestureEnabled">false</bool>
+  <string name="config_icon_mask" translatable="false">"M50 0C77.6 0 100 22.4 100 50C100 77.6 77.6 100 50 100C22.4 100 0 77.6 0 50C0 22.4 22.4 0 50 0Z"</string>
+  <bool name="config_useRoundIcon">true</bool>
+  <integer translatable="false" name="config_brightness_ramp_rate_fast">400</integer>
+  <integer translatable="false" name="config_brightness_ramp_rate_slow">400</integer>
+  <integer name="config_autoBrightnessInitialLightSensorRate">100</integer>
+  <bool name="config_skipScreenOnBrightnessRamp">true</bool>
+  <integer-array name="config_longPressVibePattern">
+     <item>1</item>
+     <item>0</item>
+  </integer-array>
+  <bool name="config_batterySaverStickyBehaviourDisabled">true</bool>
+  <bool name="config_quickSettingsSupported">false</bool>
+  <integer name="config_defaultUiModeType">6</integer>
+  <bool name="config_lockUiMode">true</bool>
+  <integer name="config_longPressOnStemPrimaryBehavior">1</integer>
+  <integer name="config_doublePressOnStemPrimaryBehavior">1</integer>
+  <integer name="config_shortPressOnStemPrimaryBehavior">1</integer>
+  <integer name="config_triplePressOnStemPrimaryBehavior">1</integer>
+  <integer name="config_doublePressOnPowerBehavior">3</integer>
+  <integer name="config_veryLongPressTimeout">3000</integer>
+  <integer name="config_mashPressOnPowerBehavior">1</integer>
+  <integer name="config_mashPressVibrateTimeOnPowerButton">500</integer>
+  <item name="config_wallpaperMinScale" format="float" type="dimen">0</item>
+  <item name="config_wallpaperMaxScale" format="float" type="dimen">1</item>
+  <bool name="config_alwaysScaleWallpaper">true</bool>
+  <integer-array name="config_wakeup_based_screen_timeouts">
+    <item>0</item>
+    <item>0</item>
+    <item>0</item>
+    <item>0</item>
+    <item>0</item>
+    <item>0</item>
+    <item>0</item>
+    <item>0</item>
+    <item>0</item>
+    <item>0</item>
+    <item>6350</item>
+  </integer-array>
+  <bool name="config_preventTranslucentTaskTransitUpdateToActivity">true</bool>
+  <bool name="config_wearAllowAllAppsForegroundLocation">true</bool>
+  <bool name="config_telephonySingleSimDefaultSubscription">false</bool>
+  <bool name="config_disableTaskSnapshots">true</bool>
+</resources>
diff --git a/shared/wear/wear_excluded_hardware.xml b/shared/wear/wear_excluded_hardware.xml
new file mode 100644
index 0000000..e6f4be9
--- /dev/null
+++ b/shared/wear/wear_excluded_hardware.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<permissions>
+    <unavailable-feature name="android.hardware.usb.accessory" />
+    <unavailable-feature name="android.hardware.usb.host" />
+</permissions>
diff --git a/shared/wear/wearable-1024.prop b/shared/wear/wearable-1024.prop
new file mode 100644
index 0000000..d13ff5d
--- /dev/null
+++ b/shared/wear/wearable-1024.prop
@@ -0,0 +1,19 @@
+ro.lmk.critical_upgrade=true
+ro.lmk.upgrade_pressure=40
+ro.lmk.downgrade_pressure=60
+ro.lmk.kill_heaviest_task=false
+
+pm.dexopt.bg-dexopt=speed-profile
+pm.dexopt.downgrade_after_inactive_days=10
+pm.dexopt.first-boot=verify
+pm.dexopt.install=quicken
+pm.dexopt.shared=quicken
+
+dalvik.vm.dex2oat-swap=true
+dalvik.vm.heapstartsize=8m
+dalvik.vm.heapgrowthlimit=96m
+dalvik.vm.heapsize=128m
+dalvik.vm.heaptargetutilization=0.75
+dalvik.vm.heapminfree=512k
+dalvik.vm.heapmaxfree=8m
+dalvik.vm.foreground-heap-growth-multiplier=2.0
diff --git a/tests/hal/hal_implementation_test.cpp b/tests/hal/hal_implementation_test.cpp
index d05f077..eaefd87 100644
--- a/tests/hal/hal_implementation_test.cpp
+++ b/tests/hal/hal_implementation_test.cpp
@@ -17,15 +17,16 @@
 #include <android-base/logging.h>
 #include <android-base/strings.h>
 #include <gtest/gtest.h>
-#include <hidl/metadata.h>
 #include <hidl-util/FQName.h>
+#include <hidl/metadata.h>
 #include <vintf/VintfObject.h>
 
 using namespace android;
 
+// clang-format off
 static const std::set<std::string> kKnownMissingHidl = {
-    "android.frameworks.bufferhub@1.0",
     "android.frameworks.cameraservice.device@2.1",
+    "android.frameworks.displayservice@1.0", // deprecated, see b/141930622
     "android.frameworks.schedulerservice@1.0", // deprecated, see b/37226359
     "android.frameworks.vr.composer@1.0",
     "android.frameworks.vr.composer@2.0",
@@ -47,22 +48,32 @@
     "android.hardware.automotive.vehicle@2.0",
     "android.hardware.biometrics.fingerprint@2.3",
     "android.hardware.bluetooth.a2dp@1.0",
+    "android.hardware.bluetooth.audio@2.1", // converted to AIDL, see b/203490261
     "android.hardware.broadcastradio@1.1",
     "android.hardware.broadcastradio@2.0",
+    "android.hardware.camera.provider@2.7", // Camera converted to AIDL, b/196432585
     "android.hardware.cas.native@1.0",
-    "android.hardware.confirmationui@1.0",
     "android.hardware.configstore@1.1", // deprecated, see b/149050985, b/149050733
+    "android.hardware.contexthub@1.2",
+    "android.hardware.drm@1.4", // converted to AIDL, b/200055138
     "android.hardware.fastboot@1.1",
+    "android.hardware.dumpstate@1.1", // deprecated, see b/205760700
+    "android.hardware.gnss@1.1", // GNSS converted to AIDL, b/206670536
+    "android.hardware.gnss@2.1", // GNSS converted to AIDL, b/206670536
     "android.hardware.gnss.measurement_corrections@1.1", // is sub-interface of gnss
     "android.hardware.gnss.visibility_control@1.0",
     "android.hardware.graphics.allocator@2.0",
     "android.hardware.graphics.allocator@3.0",
+    "android.hardware.graphics.allocator@4.0", // converted to AIDL, see b/205761012
     "android.hardware.graphics.bufferqueue@1.0",
     "android.hardware.graphics.bufferqueue@2.0",
+    "android.hardware.graphics.composer@2.4", // converted to AIDL, see b/193240715
     "android.hardware.graphics.mapper@2.1",
     "android.hardware.graphics.mapper@3.0",
     "android.hardware.health.storage@1.0", // converted to AIDL, see b/177470478
-    "android.hardware.ir@1.0",
+    "android.hardware.health@2.1", // converted to AIDL, see b/177269435
+    "android.hardware.input.classifier@1.0", // converted to AIDL, see b/205761620
+    "android.hardware.ir@1.0", // converted to AIDL, see b/205000342
     "android.hardware.keymaster@3.0",
     "android.hardware.keymaster@4.1", // Replaced by KeyMint
     "android.hardware.light@2.0",
@@ -73,235 +84,348 @@
     "android.hardware.oemlock@1.0",
     "android.hardware.power@1.3",
     "android.hardware.power.stats@1.0",
+    "android.hardware.radio@1.6", // converted to AIDL
+    "android.hardware.radio.config@1.3", // converted to AIDL
     "android.hardware.radio.deprecated@1.0",
     "android.hardware.renderscript@1.0",
     "android.hardware.soundtrigger@2.3",
     "android.hardware.secure_element@1.2",
     "android.hardware.sensors@1.0",
+    "android.hardware.sensors@2.1",
     "android.hardware.tetheroffload.config@1.0",
     "android.hardware.tetheroffload.control@1.1", // see b/170699770
     "android.hardware.thermal@1.1",
     "android.hardware.tv.cec@1.1",
     "android.hardware.tv.input@1.0",
     "android.hardware.tv.tuner@1.1",
-    "android.hardware.usb@1.3",
+    "android.hardware.usb@1.3", // migrated to AIDL see b/200993386
     "android.hardware.usb.gadget@1.2",
     "android.hardware.vibrator@1.3",
     "android.hardware.vr@1.0",
     "android.hardware.weaver@1.0",
-    "android.hardware.wifi@1.5",
     "android.hardware.wifi.hostapd@1.3",
+    "android.hardware.wifi.supplicant@1.4",
     "android.hardware.wifi.offload@1.0",
     "android.hidl.base@1.0",
     "android.hidl.memory.token@1.0",
+    "android.system.suspend@1.0", // Converted to AIDL (see b/170260236)
+};
+// clang-format on
+
+struct VersionedAidlPackage {
+  std::string name;
+  size_t version;
+  bool operator<(const VersionedAidlPackage& rhs) const {
+    return (name < rhs.name || (name == rhs.name && version < rhs.version));
+  }
 };
 
-static const std::set<std::string> kKnownMissingAidl = {
+static const std::set<VersionedAidlPackage> kKnownMissingAidl = {
+    // Cuttlefish Identity Credential HAL implementation is currently
+    // stuck at version 3 while RKP support is being added. Will be
+    // updated soon.
+    {"android.hardware.identity.", 4},
+
     // types-only packages, which never expect a default implementation
-    "android.hardware.biometrics.common.",
-    "android.hardware.common.",
-    "android.hardware.common.fmq.",
-    "android.hardware.graphics.common.",
+    {"android.hardware.audio.common.", 1},
+    {"android.hardware.biometrics.common.", 1},
+    {"android.hardware.biometrics.common.", 2},
+    {"android.hardware.common.", 1},
+    {"android.hardware.common.", 2},
+    {"android.hardware.common.fmq.", 1},
+
+    {"android.hardware.graphics.common.", 1},
+    {"android.hardware.graphics.common.", 2},
+    {"android.hardware.graphics.common.", 3},
+    {"android.hardware.input.common.", 1},
+
+    // android.hardware.camera.device is an interface returned by
+    // android.hardware.camera.provider.
+    // android.hardware.camera.common and android.hardware.camera.metadata are
+    // types used by android.hardware.camera.provider and
+    // android.hardware.camera.device.
+    {"android.hardware.camera.common.", 1},
+    {"android.hardware.camera.device.", 1},
+    {"android.hardware.camera.metadata.", 1},
+
+    // No implementations on cuttlefish for omapi aidl hal
+    {"android.se.omapi.", 1},
 
     // These KeyMaster types are in an AIDL types-only HAL because they're used
     // by the Identity Credential AIDL HAL. Remove this when fully porting
     // KeyMaster to AIDL.
-    "android.hardware.keymaster.",
+    {"android.hardware.keymaster.", 1},
+    {"android.hardware.keymaster.", 2},
+    {"android.hardware.keymaster.", 3},
+
+    // Sound trigger doesn't have a default implementation.
+    {"android.hardware.soundtrigger3.", 1},
+    {"android.media.soundtrigger.", 1},
+    {"android.media.audio.common.", 1},
 
     // These types are only used in Automotive.
-    "android.automotive.computepipe.registry.",
-    "android.automotive.computepipe.runner.",
-    "android.automotive.watchdog.",
-    "android.frameworks.automotive.powerpolicy.",
-    "android.frameworks.automotive.telemetry.",
-    "android.hardware.automotive.audiocontrol.",
-    "android.hardware.automotive.occupant_awareness.",
+    {"android.automotive.computepipe.registry.", 1},
+    {"android.automotive.computepipe.runner.", 1},
+    {"android.automotive.watchdog.", 2},
+    {"android.automotive.watchdog.", 3},
+    {"android.frameworks.automotive.display.", 1},
+    {"android.frameworks.automotive.powerpolicy.", 1},
+    {"android.frameworks.automotive.powerpolicy.internal.", 1},
+    {"android.frameworks.automotive.telemetry.", 1},
+    {"android.hardware.automotive.audiocontrol.", 1},
+    {"android.hardware.automotive.audiocontrol.", 2},
+    {"android.hardware.automotive.evs.", 1},
+    {"android.hardware.automotive.occupant_awareness.", 1},
+    {"android.hardware.automotive.vehicle.", 1},
+
+    // These types are only used in TV.
+    {"android.hardware.tv.tuner.", 1},
+
+    // types-only packages, which never expect a default implementation
+    {"android.hardware.radio.", 1},
+
+    // types-only packages, which never expect a default implementation
+    {"android.hardware.uwb.fira_android.", 1},
+};
+
+static const std::set<VersionedAidlPackage> kComingSoonAidl = {
 };
 
 // AOSP packages which are never considered
 static bool isHidlPackageConsidered(const FQName& name) {
-    static std::vector<std::string> gAospExclude = {
-        // packages not implemented now that we never expect to be implemented
-        "android.hardware.tests",
-        // packages not registered with hwservicemanager, usually sub-interfaces
-        "android.hardware.camera.device",
-    };
-    for (const std::string& package : gAospExclude) {
-        if (name.inPackage(package)) {
-            return false;
-        }
+  static std::vector<std::string> gAospExclude = {
+      // packages not implemented now that we never expect to be implemented
+      "android.hardware.tests",
+      // packages not registered with hwservicemanager, usually sub-interfaces
+      "android.hardware.camera.device",
+  };
+  for (const std::string& package : gAospExclude) {
+    if (name.inPackage(package)) {
+      return false;
     }
-    return true;
+  }
+  return true;
 }
 
 static bool isAospHidlInterface(const FQName& name) {
-    static const std::vector<std::string> kAospPackages = {
-        "android.hidl",
-        "android.hardware",
-        "android.frameworks",
-        "android.system",
-    };
-    for (const std::string& package : kAospPackages) {
-        if (name.inPackage(package)) {
-            return true;
-        }
+  static const std::vector<std::string> kAospPackages = {
+      "android.hidl",
+      "android.hardware",
+      "android.frameworks",
+      "android.system",
+  };
+  for (const std::string& package : kAospPackages) {
+    if (name.inPackage(package)) {
+      return true;
     }
-    return false;
+  }
+  return false;
 }
 
 static std::set<FQName> allTreeHidlInterfaces() {
-    std::set<FQName> ret;
-    for (const auto& iface : HidlInterfaceMetadata::all()) {
-        FQName f;
-        CHECK(f.setTo(iface.name)) << iface.name;
-        ret.insert(f);
-    }
-    return ret;
+  std::set<FQName> ret;
+  for (const auto& iface : HidlInterfaceMetadata::all()) {
+    FQName f;
+    CHECK(f.setTo(iface.name)) << iface.name;
+    ret.insert(f);
+  }
+  return ret;
 }
 
 static std::set<FQName> allHidlManifestInterfaces() {
-    std::set<FQName> ret;
-    auto setInserter = [&] (const vintf::ManifestInstance& i) -> bool {
-        if (i.format() != vintf::HalFormat::HIDL) {
-            return true;  // continue
-        }
-        ret.insert(i.getFqInstance().getFqName());
-        return true;  // continue
-    };
-    vintf::VintfObject::GetDeviceHalManifest()->forEachInstance(setInserter);
-    vintf::VintfObject::GetFrameworkHalManifest()->forEachInstance(setInserter);
-    return ret;
+  std::set<FQName> ret;
+  auto setInserter = [&](const vintf::ManifestInstance& i) -> bool {
+    if (i.format() != vintf::HalFormat::HIDL) {
+      return true;  // continue
+    }
+    ret.insert(i.getFqInstance().getFqName());
+    return true;  // continue
+  };
+  vintf::VintfObject::GetDeviceHalManifest()->forEachInstance(setInserter);
+  vintf::VintfObject::GetFrameworkHalManifest()->forEachInstance(setInserter);
+  return ret;
 }
 
 static bool isAospAidlInterface(const std::string& name) {
-    return base::StartsWith(name, "android.") &&
-        !base::StartsWith(name, "android.hardware.tests.") &&
-        !base::StartsWith(name, "android.aidl.tests");
+  return base::StartsWith(name, "android.") &&
+         !base::StartsWith(name, "android.hardware.tests.") &&
+         !base::StartsWith(name, "android.aidl.tests");
 }
 
-static std::set<std::string> allAidlManifestInterfaces() {
-    std::set<std::string> ret;
-    auto setInserter = [&] (const vintf::ManifestInstance& i) -> bool {
-        if (i.format() != vintf::HalFormat::AIDL) {
-            return true;  // continue
-        }
-        ret.insert(i.package() + "." + i.interface());
-        return true;  // continue
-    };
-    vintf::VintfObject::GetDeviceHalManifest()->forEachInstance(setInserter);
-    vintf::VintfObject::GetFrameworkHalManifest()->forEachInstance(setInserter);
-    return ret;
+static std::set<VersionedAidlPackage> allAidlManifestInterfaces() {
+  std::set<VersionedAidlPackage> ret;
+  auto setInserter = [&](const vintf::ManifestInstance& i) -> bool {
+    if (i.format() != vintf::HalFormat::AIDL) {
+      return true;  // continue
+    }
+    ret.insert({i.package() + "." + i.interface(), i.version().minorVer});
+    return true;  // continue
+  };
+  vintf::VintfObject::GetDeviceHalManifest()->forEachInstance(setInserter);
+  vintf::VintfObject::GetFrameworkHalManifest()->forEachInstance(setInserter);
+  return ret;
 }
 
 TEST(Hal, AllHidlInterfacesAreInAosp) {
-    for (const FQName& name : allHidlManifestInterfaces()) {
-      EXPECT_TRUE(isAospHidlInterface(name))
-          << "This device should only have AOSP interfaces, not: "
-          << name.string();
-    }
+  for (const FQName& name : allHidlManifestInterfaces()) {
+    EXPECT_TRUE(isAospHidlInterface(name))
+        << "This device should only have AOSP interfaces, not: "
+        << name.string();
+  }
 }
 
 TEST(Hal, HidlInterfacesImplemented) {
-    // instances -> major version -> minor versions
-    std::map<std::string, std::map<size_t, std::set<size_t>>> unimplemented;
+  // instances -> major version -> minor versions
+  std::map<std::string, std::map<size_t, std::set<size_t>>> unimplemented;
 
-    for (const FQName& f : allTreeHidlInterfaces()) {
-        if (!isAospHidlInterface(f)) continue;
-        if (!isHidlPackageConsidered(f)) continue;
+  for (const FQName& f : allTreeHidlInterfaces()) {
+    if (!isAospHidlInterface(f)) continue;
+    if (!isHidlPackageConsidered(f)) continue;
 
-        unimplemented[f.package()][f.getPackageMajorVersion()].insert(f.getPackageMinorVersion());
+    unimplemented[f.package()][f.getPackageMajorVersion()].insert(
+        f.getPackageMinorVersion());
+  }
+
+  // we'll be removing items from this which we know are missing
+  // in order to be left with those elements which we thought we
+  // knew were missing but are actually present
+  std::set<std::string> thoughtMissing = kKnownMissingHidl;
+
+  for (const FQName& f : allHidlManifestInterfaces()) {
+    if (thoughtMissing.erase(f.getPackageAndVersion().string()) > 0) {
+      ADD_FAILURE() << "Instance in missing list, but available: "
+                    << f.string();
     }
 
-    // we'll be removing items from this which we know are missing
-    // in order to be left with those elements which we thought we
-    // knew were missing but are actually present
-    std::set<std::string> thoughtMissing = kKnownMissingHidl;
+    std::set<size_t>& minors =
+        unimplemented[f.package()][f.getPackageMajorVersion()];
+    size_t minor = f.getPackageMinorVersion();
 
-    for (const FQName& f : allHidlManifestInterfaces()) {
-        if (thoughtMissing.erase(f.getPackageAndVersion().string()) > 0) {
-             ADD_FAILURE() << "Instance in missing list, but available: " << f.string();
-        }
+    auto it = minors.find(minor);
+    if (it == minors.end()) continue;
 
-        std::set<size_t>& minors = unimplemented[f.package()][f.getPackageMajorVersion()];
-        size_t minor = f.getPackageMinorVersion();
+    // if 1.2 is implemented, also considere 1.0, 1.1 implemented
+    minors.erase(minors.begin(), std::next(it));
+  }
 
-        auto it = minors.find(minor);
-        if (it == minors.end()) continue;
+  for (const auto& [package, minorsPerMajor] : unimplemented) {
+    for (const auto& [major, minors] : minorsPerMajor) {
+      if (minors.empty()) continue;
 
-        // if 1.2 is implemented, also considere 1.0, 1.1 implemented
-        minors.erase(minors.begin(), std::next(it));
+      size_t maxMinor = *minors.rbegin();
+
+      FQName missing;
+      ASSERT_TRUE(missing.setTo(package, major, maxMinor));
+
+      if (thoughtMissing.erase(missing.string()) > 0) continue;
+
+      ADD_FAILURE() << "Missing implementation from " << missing.string();
     }
+  }
 
-    for (const auto& [package, minorsPerMajor] : unimplemented) {
-        for (const auto& [major, minors] : minorsPerMajor) {
-            if (minors.empty()) continue;
-
-            size_t maxMinor = *minors.rbegin();
-
-            FQName missing;
-            ASSERT_TRUE(missing.setTo(package, major, maxMinor));
-
-            if (thoughtMissing.erase(missing.string()) > 0) continue;
-
-            ADD_FAILURE() << "Missing implementation from " << missing.string();
-        }
-    }
-
-    for (const std::string& missing : thoughtMissing) {
-        ADD_FAILURE() << "Instance in missing list and cannot find it anywhere: " << missing
-                  << " (multiple versions in missing list?)";
-    }
+  for (const std::string& missing : thoughtMissing) {
+    ADD_FAILURE() << "Instance in missing list and cannot find it anywhere: "
+                  << missing << " (multiple versions in missing list?)";
+  }
 }
 
 TEST(Hal, AllAidlInterfacesAreInAosp) {
-    for (const std::string& name : allAidlManifestInterfaces()) {
-      EXPECT_TRUE(isAospAidlInterface(name))
-          << "This device should only have AOSP interfaces, not: " << name;
-    }
+  for (const auto& package : allAidlManifestInterfaces()) {
+    EXPECT_TRUE(isAospAidlInterface(package.name))
+        << "This device should only have AOSP interfaces, not: "
+        << package.name;
+  }
 }
 
 // android.hardware.foo.IFoo -> android.hardware.foo.
 std::string getAidlPackage(const std::string& aidlType) {
-    size_t lastDot = aidlType.rfind('.');
-    CHECK(lastDot != std::string::npos);
-    return aidlType.substr(0, lastDot + 1);
+  size_t lastDot = aidlType.rfind('.');
+  CHECK(lastDot != std::string::npos);
+  return aidlType.substr(0, lastDot + 1);
 }
 
+struct AidlPackageCheck {
+  bool hasRegistration;
+  bool knownMissing;
+};
+
 TEST(Hal, AidlInterfacesImplemented) {
-    std::set<std::string> manifest = allAidlManifestInterfaces();
-    std::set<std::string> thoughtMissing = kKnownMissingAidl;
+  std::set<VersionedAidlPackage> manifest = allAidlManifestInterfaces();
+  std::set<VersionedAidlPackage> thoughtMissing = kKnownMissingAidl;
+  std::set<VersionedAidlPackage> comingSoon = kComingSoonAidl;
 
-    for (const auto& iface : AidlInterfaceMetadata::all()) {
-        ASSERT_FALSE(iface.types.empty()) << iface.name;  // sanity
-        if (std::none_of(iface.types.begin(), iface.types.end(), isAospAidlInterface)) continue;
-        if (iface.stability != "vintf") continue;
+  for (const auto& treePackage : AidlInterfaceMetadata::all()) {
+    ASSERT_FALSE(treePackage.types.empty()) << treePackage.name;
+    if (std::none_of(treePackage.types.begin(), treePackage.types.end(),
+                     isAospAidlInterface))
+      continue;
+    if (treePackage.stability != "vintf") continue;
 
-        bool hasRegistration = false;
-        bool knownMissing = false;
-        for (const std::string& type : iface.types) {
-            if (manifest.erase(type) > 0) hasRegistration = true;
-            if (thoughtMissing.erase(getAidlPackage(type)) > 0)  knownMissing = true;
+    // expect versions from 1 to latest version. If the package has development
+    // the latest version is the latest known version + 1. Each of these need
+    // to be checked for registration and knownMissing.
+    std::map<size_t, AidlPackageCheck> expectedVersions;
+    for (const auto version : treePackage.versions) {
+      expectedVersions[version] = {false, false};
+    }
+    if (treePackage.has_development) {
+      size_t version =
+          treePackage.versions.empty() ? 1 : *treePackage.versions.rbegin() + 1;
+      expectedVersions[version] = {false, false};
+    }
+
+    // Check all types and versions defined by the package for registration.
+    // The package version is considered registered if any of those types are
+    // present in the manifest with the same version.
+    // The package version is considered known missing if it is found in
+    // thoughtMissing.
+    bool latestRegistered = false;
+    for (const std::string& type : treePackage.types) {
+      for (auto& [version, check] : expectedVersions) {
+        if (manifest.erase({type, version}) > 0) {
+          if (version == expectedVersions.rbegin()->first) {
+            latestRegistered = true;
+          }
+          check.hasRegistration = true;
+        }
+        if (thoughtMissing.erase({getAidlPackage(type), version}) > 0)
+          check.knownMissing = true;
+      }
+    }
+
+    if (!latestRegistered && !expectedVersions.rbegin()->second.knownMissing) {
+      ADD_FAILURE() << "The latest version ("
+                    << expectedVersions.rbegin()->first
+                    << ") of the package is not implemented: "
+                    << treePackage.name
+                    << " which declares the following types:\n    "
+                    << base::Join(treePackage.types, "\n    ");
+    }
+
+    for (const auto& [version, check] : expectedVersions) {
+      if (check.knownMissing) {
+        if (check.hasRegistration) {
+          ADD_FAILURE() << "Package in missing list, but available: "
+                        << treePackage.name << " V" << version
+                        << " which declares the following types:\n    "
+                        << base::Join(treePackage.types, "\n    ");
         }
 
-        if (knownMissing) {
-            if (hasRegistration) {
-                ADD_FAILURE() << "Interface in missing list, but available: " << iface.name
-                          << " which declares the following types:\n    "
-                          << base::Join(iface.types, "\n    ");
-            }
-
-            continue;
-        }
-
-        EXPECT_TRUE(hasRegistration) << iface.name << " which declares the following types:\n    "
-            << base::Join(iface.types, "\n    ");
+        continue;
+      }
     }
+  }
 
-    for (const std::string& iface : thoughtMissing) {
-        ADD_FAILURE() << "Interface in manifest list and cannot find it anywhere: " << iface;
+  for (const auto& package : thoughtMissing) {
+    // TODO: b/194806512 : Remove after Wifi hostapd AIDL interface lands on aosp
+    if (comingSoon.erase(package) == 0) {
+      ADD_FAILURE() << "Interface in missing list and cannot find it anywhere: "
+                    << package.name << " V" << package.version;
     }
+  }
 
-    for (const std::string& iface : manifest) {
-        ADD_FAILURE() << "Can't find manifest entry in tree: " << iface;
-    }
+  for (const auto& package : manifest) {
+    ADD_FAILURE() << "Can't find manifest entry in tree: " << package.name
+                  << " version: " << package.version;
+  }
 }
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
new file mode 100644
index 0000000..89ef7b5
--- /dev/null
+++ b/tests/integration/Android.bp
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CuttlefishIntegrationTest",
+
+    auto_gen_config: false, // Use the AndroidTest.xml file in this directory
+    test_config: "AndroidTest.xml",
+
+    srcs: ["com/**/*.java"],
+
+    data_native_bins: ["cvd_test_gce_driver"],
+
+    libs: [
+        "auto_value_annotations",
+        "libprotobuf-java-full",
+        "guava",
+        "guice-no-guava",
+        "junit",
+        "tradefed",
+    ],
+    static_libs: [
+        "libcuttlefish_test_gce_proto_java",
+    ],
+
+    plugins: ["auto_value_plugin", "auto_annotation_plugin"],
+    test_suites: ["general-tests"],
+}
diff --git a/tests/integration/AndroidTest.xml b/tests/integration/AndroidTest.xml
new file mode 100644
index 0000000..7885ad1
--- /dev/null
+++ b/tests/integration/AndroidTest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs CuttlefishIntegrationTest">
+    <option name="test-suite-tag" value="cuttlefish_integration_test" />
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="jar" value="CuttlefishIntegrationTest.jar" />
+    </test>
+</configuration>
diff --git a/tests/integration/com/android/cuttlefish/test/AcloudLocalInstanceTest.java b/tests/integration/com/android/cuttlefish/test/AcloudLocalInstanceTest.java
new file mode 100644
index 0000000..9fd11a3
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/AcloudLocalInstanceTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeNotNull;
+
+import com.google.inject.Inject;
+import java.io.File;
+import javax.annotation.Nullable;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(CuttlefishIntegrationTestRunner.class)
+public class AcloudLocalInstanceTest {
+  @Inject(optional = true)
+  @SetOption("gce-driver-service-account-json-key-path")
+  @Nullable
+  private String gceJsonKeyPath = null;
+
+  @Inject @Rule public GceInstanceRule gceInstance;
+  @Inject private BuildChooser buildChooser;
+
+  @Test
+  public void launchAcloudPrebuilt() throws Exception {
+    assumeNotNull(gceJsonKeyPath);
+    gceInstance.uploadFile(new File(gceJsonKeyPath), "key.json");
+    assertEquals(0, gceInstance.ssh("sudo", "apt-get", "install", "adb", "-y").returnCode());
+    gceInstance.uploadBuildArtifact("acloud_prebuilt", "acloud");
+    assertEquals(0, gceInstance.ssh("chmod", "+x", "acloud").returnCode());
+    // TODO(schuffelen): Make this choose the current build
+    assertEquals(0,
+        gceInstance
+            .ssh("./acloud", "create", "-y", "--local-instance", "--skip-pre-run-check",
+                "--service-account-json-private-key-path=key.json",
+                "--build-id=" + buildChooser.buildId(),
+                "--build-target=" + buildChooser.buildFlavor())
+            .returnCode());
+  }
+
+  @Test
+  public void launchAcloudDev() throws Exception {
+    assumeNotNull(gceJsonKeyPath);
+    gceInstance.uploadFile(new File(gceJsonKeyPath), "key.json");
+    assertEquals(0, gceInstance.ssh("sudo", "apt-get", "install", "adb", "-y").returnCode());
+    gceInstance.uploadBuildArtifact("acloud-dev", "acloud");
+    assertEquals(0, gceInstance.ssh("chmod", "+x", "acloud").returnCode());
+    // TODO(schuffelen): Make this choose the current build
+    assertEquals(0,
+        gceInstance
+            .ssh("./acloud", "create", "-y", "--local-instance", "--skip-pre-run-check",
+                "--service-account-json-private-key-path=key.json",
+                "--build-id=" + buildChooser.buildId(),
+                "--build-target=" + buildChooser.buildFlavor())
+            .returnCode());
+  }
+}
diff --git a/tests/integration/com/android/cuttlefish/test/BuildChooser.java b/tests/integration/com/android/cuttlefish/test/BuildChooser.java
new file mode 100644
index 0000000..38cc963
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/BuildChooser.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.google.inject.Inject;
+import javax.annotation.Nullable;
+
+/**
+ * Manager for a dedicated GCE instance for every @Test function.
+ *
+ * Must be constructed through Guice injection. Calls out to the cvd_test_gce_driver binary to
+ * create the GCE instances.
+ */
+public final class BuildChooser {
+  @Inject(optional = true)
+  @SetOption("build-flavor")
+  @Nullable
+  private String buildFlavorOption = null;
+
+  @Inject(optional = true) @SetOption("build-id") @Nullable private String buildIdOption = null;
+
+  @Inject private IBuildInfo buildInfo;
+
+  public String fetchCvdBuild() {
+    return buildId() + "/" + buildFlavor();
+  }
+
+  public Build buildProto() {
+    return Build.newBuilder().setId(buildId()).setTarget(buildFlavor()).build();
+  }
+
+  public String buildFlavor() {
+    if (buildFlavorOption != null) {
+      return buildFlavorOption;
+    }
+    return buildInfo.getBuildFlavor();
+  }
+
+  public String buildId() {
+    if (buildIdOption != null) {
+      return buildIdOption;
+    }
+    return buildInfo.getBuildId();
+  }
+}
diff --git a/tests/integration/com/android/cuttlefish/test/CuttlefishIntegrationTestRunner.java b/tests/integration/com/android/cuttlefish/test/CuttlefishIntegrationTestRunner.java
new file mode 100644
index 0000000..5ba6f9f
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/CuttlefishIntegrationTestRunner.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.HostTest;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.ISetOptionReceiver;
+import com.android.tradefed.testtype.ITestInformationReceiver;
+import com.google.auto.value.AutoAnnotation;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.TestClass;
+
+public final class CuttlefishIntegrationTestRunner extends BlockJUnit4ClassRunner
+    implements ITestInformationReceiver, ISetOptionReceiver, IBuildReceiver {
+  @Option(name = HostTest.SET_OPTION_NAME, description = HostTest.SET_OPTION_DESC)
+  private HashSet<String> keyValueOptions = new HashSet<>();
+
+  private IBuildInfo buildInfo;
+  private TestInformation testInfo;
+  private final TestClass testClass;
+
+  // Required by JUnit
+  public CuttlefishIntegrationTestRunner(Class<?> testClass) throws InitializationError {
+    this(new TestClass(testClass));
+  }
+
+  private CuttlefishIntegrationTestRunner(TestClass testClass) throws InitializationError {
+    super(testClass);
+    this.testClass = testClass;
+  }
+
+  @Override
+  public void setBuild(IBuildInfo buildInfo) {
+    this.buildInfo = checkNotNull(buildInfo);
+  }
+
+  @Override
+  public void setTestInformation(TestInformation testInfo) {
+    this.testInfo = checkNotNull(testInfo);
+  }
+
+  @Override
+  public TestInformation getTestInformation() {
+    return checkNotNull(testInfo);
+  }
+
+  private ImmutableMap<String, String> processOptions() {
+    // Regex from HostTest.setOptionToLoadedObject
+    String delim = ":";
+    String esc = "\\";
+    String regex = "(?<!" + Pattern.quote(esc) + ")" + Pattern.quote(delim);
+    ImmutableMap.Builder<String, String> optMap = ImmutableMap.builder();
+    for (String item : keyValueOptions) {
+      String[] fields = item.split(regex);
+      checkState(fields.length == 2, "Could not parse \"%s\"", item);
+      String value = fields[1].replaceAll(Pattern.quote(esc) + Pattern.quote(delim), delim);
+      optMap.put(fields[0], value);
+    }
+    return optMap.build();
+  }
+
+  @AutoAnnotation
+  private static SetOption setOptionAnnotation(String value) {
+    return new AutoAnnotation_CuttlefishIntegrationTestRunner_setOptionAnnotation(value);
+  }
+
+  private void bindOptions(Binder binder) {
+    // TODO(schuffelen): Handle collections and maps
+    for (Map.Entry<String, String> option : processOptions().entrySet()) {
+      SetOption annotation = setOptionAnnotation(option.getKey());
+      binder.bind(String.class).annotatedWith(annotation).toInstance(option.getValue());
+    }
+  }
+
+  private final class TradefedClassesModule extends AbstractModule {
+    @Override
+    protected void configure() {
+      bind(TestInformation.class).toInstance(testInfo);
+      bind(IBuildInfo.class).toInstance(buildInfo);
+      bindOptions(binder());
+    }
+  }
+
+  @Override
+  protected Object createTest() {
+    Injector injector = Guice.createInjector(new TradefedClassesModule());
+    return injector.getInstance(testClass.getJavaClass());
+  }
+}
diff --git a/tests/integration/com/android/cuttlefish/test/GceInstanceRule.java b/tests/integration/com/android/cuttlefish/test/GceInstanceRule.java
new file mode 100644
index 0000000..a3889b5
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/GceInstanceRule.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.cuttlefish.test.DataType;
+import com.android.cuttlefish.test.Exit;
+import com.android.cuttlefish.test.TestMessage;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.FileUtil;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Inject;
+import com.google.protobuf.ByteString;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.util.UUID;
+import javax.annotation.Nullable;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Manager for a dedicated GCE instance for every @Test function.
+ *
+ * Must be constructed through Guice injection. Calls out to the cvd_test_gce_driver binary to
+ * create the GCE instances.
+ */
+public final class GceInstanceRule implements TestRule {
+  @Inject(optional = true)
+  @SetOption("gce-driver-service-account-json-key-path")
+  @Nullable
+  private String gceJsonKeyPath = null;
+
+  @Inject(optional = true) @SetOption("cloud-project") private String cloudProject;
+
+  @Inject(optional = true) @SetOption("zone") private String zone = "us-west1-a";
+
+  @Inject(optional = true)
+  @SetOption("internal-addresses")
+  private boolean internal_addresses = false;
+
+  @Inject private TestInformation testInfo;
+  @Inject private BuildChooser buildChooser;
+
+  private Process driverProcess;
+  private String managedInstance;
+
+  private Process launchDriver(File gceDriver) throws IOException {
+    ImmutableList.Builder<String> cmdline = new ImmutableList.Builder();
+    cmdline.add(gceDriver.toString());
+    assumeNotNull(gceJsonKeyPath);
+    cmdline.add("--internal-addresses=" + internal_addresses);
+    cmdline.add("--cloud-project=" + cloudProject);
+    cmdline.add("--service-account-json-private-key-path=" + gceJsonKeyPath);
+    ProcessBuilder processBuilder = new ProcessBuilder(cmdline.build());
+    processBuilder.redirectInput(ProcessBuilder.Redirect.PIPE);
+    processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE);
+    processBuilder.redirectError(ProcessBuilder.Redirect.PIPE);
+    return processBuilder.start();
+  }
+
+  private static Thread launchLogger(InputStream input) {
+    BufferedReader reader = new BufferedReader(new InputStreamReader(input));
+    Thread logThread = new Thread(() -> {
+      try {
+        String line;
+        while ((line = reader.readLine()) != null) {
+          CLog.logAndDisplay(LogLevel.DEBUG, "cvd_test_gce_driver output: %s", line);
+        }
+      } catch (Exception e) {
+        CLog.logAndDisplay(LogLevel.DEBUG, "cvd_test_gce_driver exception: %s", e);
+      }
+    });
+    logThread.start();
+    return logThread;
+  }
+
+  private String createInstance() throws IOException {
+    String desiredName = "cuttlefish-integration-" + UUID.randomUUID();
+    TestMessage.Builder request = TestMessage.newBuilder();
+    request.getCreateInstanceBuilder().getIdBuilder().setName(desiredName);
+    request.getCreateInstanceBuilder().getIdBuilder().setZone(zone);
+    sendMessage(request.build());
+    ImmutableList<TestMessage> errors =
+        collectResponses().stream().filter(TestMessage::hasError).collect(toImmutableList());
+    if (errors.size() > 0) {
+      throw new IOException("Failed to create instance: " + errors);
+    }
+    return desiredName;
+  }
+
+  @AutoValue
+  public static abstract class SshResult {
+    public static SshResult create(File stdout, File stderr, int ret) {
+      return new AutoValue_GceInstanceRule_SshResult(stdout, stderr, ret);
+    }
+
+    public abstract File stdout();
+    public abstract File stderr();
+    public abstract int returnCode();
+  }
+
+  public SshResult ssh(String... command) throws IOException {
+    return ssh(ImmutableList.copyOf(command));
+  }
+
+  public SshResult ssh(ImmutableList<String> command) throws IOException {
+    TestMessage.Builder request = TestMessage.newBuilder();
+    request.getSshCommandBuilder().getInstanceBuilder().setName(managedInstance);
+    request.getSshCommandBuilder().addAllArguments(command);
+    sendMessage(request.build());
+    File stdout = FileUtil.createTempFile("ssh_", "_stdout.txt");
+    OutputStream stdoutStream = new FileOutputStream(stdout);
+    File stderr = FileUtil.createTempFile("ssh_", "_stderr.txt");
+    OutputStream stderrStream = new FileOutputStream(stderr);
+    int returnCode = -1;
+    IOException storedException = null;
+    while (true) {
+      TestMessage response = receiveMessage();
+      switch (response.getContentsCase()) {
+        case DATA:
+          if (response.getData().getType().equals(DataType.DATA_TYPE_STDOUT)) {
+            stdoutStream.write(response.getData().getContents().toByteArray());
+          } else if (response.getData().getType().equals(DataType.DATA_TYPE_STDERR)) {
+            stderrStream.write(response.getData().getContents().toByteArray());
+          } else if (response.getData().getType().equals(DataType.DATA_TYPE_RETURN_CODE)) {
+            returnCode = Integer.valueOf(response.getData().getContents().toStringUtf8());
+          } else {
+            throw new RuntimeException("Unexpected type: " + response.getData().getType());
+          }
+          break;
+        case ERROR:
+          if (storedException == null) {
+            storedException = new IOException(response.getError().getText());
+          } else {
+            storedException.addSuppressed(new IOException(response.getError().getText()));
+          }
+        case STREAM_END:
+          if (storedException == null) {
+            return SshResult.create(stdout, stderr, returnCode);
+          } else {
+            throw storedException;
+          }
+        default: {
+          IOException exception = new IOException("Unexpected message: " + response);
+          if (storedException == null) {
+            exception.addSuppressed(storedException);
+          }
+          throw exception;
+        }
+      }
+    }
+  }
+
+  public void uploadFile(File sourceFile, String destFile) throws IOException {
+    TestMessage.Builder request = TestMessage.newBuilder();
+    request.getUploadFileBuilder().getInstanceBuilder().setName(managedInstance);
+    request.getUploadFileBuilder().setRemotePath(destFile);
+
+    // Allow this to error out before initiating the transfer
+    FileInputStream stream = new FileInputStream(sourceFile);
+    sendMessage(request.build());
+    byte[] buffer = new byte[1 << 14 /* 16 KiB */];
+    int read = 0;
+    while ((read = stream.read(buffer)) != -1) {
+      TestMessage.Builder dataMessage = TestMessage.newBuilder();
+      dataMessage.getDataBuilder().setType(DataType.DATA_TYPE_FILE_CONTENTS);
+      dataMessage.getDataBuilder().setContents(ByteString.copyFrom(buffer, 0, read));
+      sendMessage(dataMessage.build());
+    }
+    TestMessage.Builder endRequest = TestMessage.newBuilder();
+    endRequest.setStreamEnd(StreamEnd.getDefaultInstance());
+    sendMessage(endRequest.build());
+    ImmutableList<TestMessage> errors =
+        collectResponses().stream().filter(TestMessage::hasError).collect(toImmutableList());
+    if (errors.size() > 0) {
+      throw new IOException("Failed to upload file: " + errors);
+    }
+  }
+
+  public void uploadBuildArtifact(String artifact, String destFile) throws IOException {
+    TestMessage.Builder request = TestMessage.newBuilder();
+    request.getUploadBuildArtifactBuilder().getInstanceBuilder().setName(managedInstance);
+    request.getUploadBuildArtifactBuilder().setBuild(buildChooser.buildProto());
+    request.getUploadBuildArtifactBuilder().setArtifactName(artifact);
+    request.getUploadBuildArtifactBuilder().setRemotePath(destFile);
+    sendMessage(request.build());
+    ImmutableList<TestMessage> errors =
+        collectResponses().stream().filter(TestMessage::hasError).collect(toImmutableList());
+    if (errors.size() > 0) {
+      throw new IOException("Failed to upload build artifact: " + errors);
+    }
+  }
+
+  private TestMessage receiveMessage() throws IOException {
+    TestMessage message = TestMessage.parser().parseDelimitedFrom(driverProcess.getInputStream());
+    CLog.logAndDisplay(LogLevel.DEBUG, "Received message \"" + message + "\"");
+    return message;
+  }
+
+  private ImmutableList<TestMessage> collectResponses() throws IOException {
+    ImmutableList.Builder<TestMessage> messages = ImmutableList.builder();
+    while (true) {
+      TestMessage received = receiveMessage();
+      messages.add(received);
+      if (received.hasStreamEnd()) {
+        return messages.build();
+      }
+    }
+  }
+
+  private void sendMessage(TestMessage message) throws IOException {
+    CLog.logAndDisplay(LogLevel.DEBUG, "Sending message \"" + message + "\"");
+    message.writeDelimitedTo(driverProcess.getOutputStream());
+    driverProcess.getOutputStream().flush();
+  }
+
+  @Override
+  public Statement apply(Statement base, Description description) {
+    final File gceDriver;
+    try {
+      gceDriver = testInfo.getDependencyFile("cvd_test_gce_driver", false);
+    } catch (FileNotFoundException e) {
+      assumeNoException("Could not find cvd_test_gce_driver", e);
+      return null;
+    }
+    assumeTrue("cvd_test_gce_driver file did not exist", gceDriver.exists());
+    return new Statement() {
+      @Override
+      public void evaluate() throws Throwable {
+        // TODO(schuffelen): Reuse instances with GCE resets.
+        // The trick will be figuring out when the instances can actually be destroyed.
+        driverProcess = launchDriver(gceDriver);
+        Thread logStderr = launchLogger(driverProcess.getErrorStream());
+        managedInstance = createInstance();
+        try {
+          base.evaluate();
+        } finally {
+          boolean cleanExit = false;
+          for (int i = 0; i < 10; i++) {
+            sendMessage(TestMessage.newBuilder().setExit(Exit.getDefaultInstance()).build());
+            TestMessage response = receiveMessage();
+            if (response.hasExit()) {
+              cleanExit = true;
+              break;
+            } else if (!response.hasError()
+                && !response.hasStreamEnd()) { // Swallow some errors to to get out if necessary
+              throw new AssertionError("Unexpected message " + response);
+            }
+          }
+          assertTrue("Failed to get an exit response", cleanExit);
+          assertEquals(0, driverProcess.waitFor());
+          logStderr.join();
+          driverProcess = null;
+        }
+      }
+    };
+  }
+}
diff --git a/tests/integration/com/android/cuttlefish/test/GceInstanceRuleTest.java b/tests/integration/com/android/cuttlefish/test/GceInstanceRuleTest.java
new file mode 100644
index 0000000..a21eab3
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/GceInstanceRuleTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import javax.inject.Inject;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(CuttlefishIntegrationTestRunner.class)
+public class GceInstanceRuleTest {
+  @Inject @Rule public GceInstanceRule gceInstance;
+
+  @Test
+  public void createInstance() {}
+
+  @Test
+  public void sshToInstance() throws Exception {
+    assertEquals(0, gceInstance.ssh("ls", "/").returnCode());
+  }
+
+  @Test
+  public void uploadBuildArtifact() throws Exception {
+    gceInstance.uploadBuildArtifact("fetch_cvd", "/home/vsoc-01/fetch_cvd");
+    assertEquals(0, gceInstance.ssh("chmod", "+x", "/home/vsoc-01/fetch_cvd").returnCode());
+  }
+}
diff --git a/tests/integration/com/android/cuttlefish/test/LaunchTest.java b/tests/integration/com/android/cuttlefish/test/LaunchTest.java
new file mode 100644
index 0000000..502e6cc
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/LaunchTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import javax.inject.Inject;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(CuttlefishIntegrationTestRunner.class)
+public class LaunchTest {
+  @Inject @Rule public GceInstanceRule gceInstance;
+  @Inject private BuildChooser buildChooser;
+
+  @Before
+  public void downloadInstanceFiles() throws Exception {
+    gceInstance.uploadBuildArtifact("fetch_cvd", "fetch_cvd");
+    assertEquals(0, gceInstance.ssh("chmod", "+x", "fetch_cvd").returnCode());
+    // TODO(schuffelen): Make this fetch the current build
+    assertEquals(0,
+        gceInstance.ssh("./fetch_cvd", "-default_build=" + buildChooser.fetchCvdBuild())
+            .returnCode());
+  }
+
+  @Test
+  public void launchDaemon() throws Exception {
+    assertEquals(0,
+        gceInstance.ssh("bin/launch_cvd", "--daemon", "--report_anonymous_usage_stats=y")
+            .returnCode());
+  }
+
+  @Test
+  public void launchDaemonStop() throws Exception {
+    assertEquals(0,
+        gceInstance.ssh("bin/launch_cvd", "--daemon", "--report_anonymous_usage_stats=y")
+            .returnCode());
+    assertEquals(0, gceInstance.ssh("bin/stop_cvd").returnCode());
+  }
+
+  @Test
+  public void launchDaemonStopLaunch() throws Exception {
+    assertEquals(0,
+        gceInstance.ssh("bin/launch_cvd", "--daemon", "--report_anonymous_usage_stats=y")
+            .returnCode());
+    assertEquals(0, gceInstance.ssh("bin/stop_cvd").returnCode());
+    assertEquals(0,
+        gceInstance.ssh("bin/launch_cvd", "--daemon", "--report_anonymous_usage_stats=y")
+            .returnCode());
+  }
+}
diff --git a/tests/integration/com/android/cuttlefish/test/SetOption.java b/tests/integration/com/android/cuttlefish/test/SetOption.java
new file mode 100644
index 0000000..ede1854
--- /dev/null
+++ b/tests/integration/com/android/cuttlefish/test/SetOption.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.test;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import javax.inject.Qualifier;
+
+/**
+ * Binding annotation for Tradefed options.
+ *
+ * If an option is given to tradefed in the form of
+ *
+ * <pre>
+ *   --test-arg com.android.tradefed.testtype.HostTest:set-option:OPTION:VALUE
+ * </pre>
+ *
+ * {@link CuttlefishIntegrationTestRunner} will create a binding of {@code SetOption("OPTION")}
+ * and to a string with contents {@code VALUE}.
+ */
+@Qualifier
+@Target({FIELD, PARAMETER, METHOD})
+@Retention(RUNTIME)
+public @interface SetOption {
+  String value();
+}
diff --git a/tests/powerwash/src/com/android/cuttlefish/tests/PowerwashTest.java b/tests/powerwash/src/com/android/cuttlefish/tests/PowerwashTest.java
index 84fa852..42d9dda 100644
--- a/tests/powerwash/src/com/android/cuttlefish/tests/PowerwashTest.java
+++ b/tests/powerwash/src/com/android/cuttlefish/tests/PowerwashTest.java
@@ -15,15 +15,19 @@
  */
 package com.android.cuttlefish.tests;
 
+import static org.junit.Assert.assertTrue;
+
 import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
+import com.android.tradefed.device.internal.DeviceResetHandler;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
-import java.io.File;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
+
 /**
  * Test powerwash function.
  *
@@ -48,14 +52,18 @@
         if (file == null) {
             Assert.fail("Setup failed: tmp file failed to persist after device reboot.");
         }
-
+        boolean success = false;
         if (getDevice() instanceof RemoteAndroidVirtualDevice) {
-            ((RemoteAndroidVirtualDevice) getDevice()).powerwashGce();
+            success = ((RemoteAndroidVirtualDevice) getDevice()).powerwashGce();
         } else {
-            Assert.fail("This test only supports running in test lab setup.");
+            // We don't usually expect tests to use our feature server, but in this case we are
+            // validating the feature itself so it's fine
+            DeviceResetHandler handler = new DeviceResetHandler(getInvocationContext());
+            success = handler.resetDevice(getDevice());
         }
+        assertTrue("Powerwash reset failed", success);
 
-        // Verify that the device is back online and pre-xisting file is gone.
+        // Verify that the device is back online and pre-existing file is gone.
         file = getDevice().pullFile(tmpFile);
         if (file != null) {
             Assert.fail("Powerwash failed: pre-existing file still exists.");
diff --git a/tests/ril/src/com/android/cuttlefish/ril/tests/RilE2eTests.java b/tests/ril/src/com/android/cuttlefish/ril/tests/RilE2eTests.java
index 3c5db3f..d633991 100644
--- a/tests/ril/src/com/android/cuttlefish/ril/tests/RilE2eTests.java
+++ b/tests/ril/src/com/android/cuttlefish/ril/tests/RilE2eTests.java
@@ -23,8 +23,8 @@
 import android.net.NetworkInfo;
 import android.net.wifi.WifiManager;
 import android.os.Build;
-import android.telephony.CellInfoGsm;
-import android.telephony.CellSignalStrengthGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellSignalStrengthLte;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
@@ -35,7 +35,6 @@
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -141,13 +140,11 @@
         Assert.assertSame(TelephonyManager.DATA_CONNECTED, mTeleManager.getDataState());
     }
 
-    // See b/74256305
-    @Ignore
     @Test
     public void testSignalLevels() throws Exception {
-        CellInfoGsm cellinfogsm = (CellInfoGsm)mTeleManager.getAllCellInfo().get(0);
-        CellSignalStrengthGsm cellSignalStrengthGsm = cellinfogsm.getCellSignalStrength();
-        int bars = cellSignalStrengthGsm.getLevel();
+        CellInfoLte cellInfo = (CellInfoLte) mTeleManager.getAllCellInfo().get(0);
+        CellSignalStrengthLte signalStrength = cellInfo.getCellSignalStrength();
+        int bars = signalStrength.getLevel();
         Assert.assertThat("Signal Bars", bars, greaterThan(1));
     }
 }
diff --git a/tools/Android.bp b/tools/Android.bp
new file mode 100644
index 0000000..fe771af
--- /dev/null
+++ b/tools/Android.bp
@@ -0,0 +1,8 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "create_base_image",
+    srcs: ["create_base_image.go"],
+}
diff --git a/tools/create_base_image.go b/tools/create_base_image.go
new file mode 100644
index 0000000..40d533b
--- /dev/null
+++ b/tools/create_base_image.go
@@ -0,0 +1,262 @@
+package main
+
+import (
+  "os"
+  "os/exec"
+  "os/user"
+  "flag"
+  "fmt"
+  "strings"
+  "io/ioutil"
+  "log"
+  "time"
+)
+
+type OnFail int
+
+const (
+    IgnoreOnFail OnFail = iota
+    WarnOnFail
+    ExitOnFail
+)
+
+var build_instance string
+var build_project string
+var build_zone string
+var dest_image string
+var dest_family string
+var dest_project string
+var launch_instance string
+var source_image_family string
+var source_image_project string
+var repository_url string
+var repository_branch string
+var version string
+var SSH_FLAGS string
+var INTERNAL_extra_source string
+var verbose bool
+var username string
+
+func init() {
+  user, err := user.Current()
+  if err != nil {
+    panic(err)
+  }
+  username = user.Username
+
+  flag.StringVar(&build_instance, "build_instance",
+    username+"-build", "Instance name to create for the build")
+  flag.StringVar(&build_project, "build_project",
+    mustShell("gcloud config get-value project"), "Project to use for scratch")
+  flag.StringVar(&build_zone, "build_zone",
+    mustShell("gcloud config get-value compute/zone"),
+    "Zone to use for scratch resources")
+  flag.StringVar(&dest_image, "dest_image",
+    "vsoc-host-scratch-"+username, "Image to create")
+  flag.StringVar(&dest_family, "dest_family", "",
+    "Image family to add the image to")
+  flag.StringVar(&dest_project, "dest_project",
+    mustShell("gcloud config get-value project"), "Project to use for the new image")
+  flag.StringVar(&launch_instance, "launch_instance", "",
+    "Name of the instance to launch with the new image")
+  flag.StringVar(&source_image_family, "source_image_family", "debian-11",
+    "Image familty to use as the base")
+  flag.StringVar(&source_image_project, "source_image_project", "debian-cloud",
+    "Project holding the base image")
+  flag.StringVar(&repository_url, "repository_url",
+    "https://github.com/google/android-cuttlefish.git",
+    "URL to the repository with host changes")
+  flag.StringVar(&repository_branch, "repository_branch",
+    "main", "Branch to check out")
+  flag.StringVar(&version, "version", "", "cuttlefish-common version")
+  flag.StringVar(&SSH_FLAGS, "INTERNAL_IP", "",
+    "INTERNAL_IP can be set to --internal-ip run on a GCE instance."+
+    "The instance will need --scope compute-rw.")
+  flag.StringVar(&INTERNAL_extra_source, "INTERNAL_extra_source", "",
+    "INTERNAL_extra_source may be set to a directory containing the source for extra packages to build.")
+  flag.BoolVar(&verbose, "verbose", true, "print commands and output (default: true)")
+  flag.Parse()
+}
+
+func shell(cmd string) (string, error) {
+  if verbose {
+    fmt.Println(cmd)
+  }
+  b, err := exec.Command("/bin/sh", "-c", cmd).CombinedOutput()
+  if verbose {
+    fmt.Println(string(b))
+  }
+  if err != nil {
+    return "", err
+  }
+  return strings.TrimSpace(string(b)), nil
+}
+
+func mustShell(cmd string) string {
+  if verbose {
+    fmt.Println(cmd)
+  }
+  out, err := shell(cmd)
+  if err != nil {
+    panic(err)
+  }
+  if verbose {
+    fmt.Println(out)
+  }
+  return strings.TrimSpace(out)
+}
+
+func gce(action OnFail, gceArg string, errorStr ...string) (string, error) {
+  cmd := "gcloud " + gceArg
+  out, err := shell(cmd)
+  if out != "" {
+    fmt.Println(out)
+  }
+  if err != nil && action != IgnoreOnFail {
+    var buf string
+    fmt.Sprintf(buf, "gcloud error occurred: %s", err)
+    if (len(errorStr) > 0) {
+      buf += " [" + errorStr[0] + "]"
+    }
+    if action == ExitOnFail {
+      panic(buf)
+    }
+    if action == WarnOnFail {
+      fmt.Println(buf)
+    }
+  }
+  return out, err
+}
+
+func waitForInstance(PZ string) {
+  for {
+    time.Sleep(5 * time.Second)
+    _, err := gce(WarnOnFail, `compute ssh `+SSH_FLAGS+` `+PZ+` `+
+                  build_instance+` -- uptime`)
+    if err == nil {
+      break
+    }
+  }
+}
+
+func packageSource(url string, branch string, version string, subdir string) {
+  repository_dir := url[strings.LastIndex(url, "/")+1:]
+  debian_dir := mustShell(`basename "`+repository_dir+`" .git`)
+  if subdir != "" {
+    debian_dir = repository_dir + "/" + subdir
+  }
+  mustShell("git clone " + url + " -b "+branch)
+  mustShell("dpkg-source -b " + debian_dir)
+  mustShell("rm -rf " + debian_dir)
+  mustShell("ls -l")
+  mustShell("pwd")
+}
+
+func createInstance(instance string, arg string) {
+  _, err := gce(WarnOnFail, `compute instances describe "`+instance+`"`)
+  if err != nil {
+    gce(ExitOnFail, `compute instances create `+arg+` "`+instance+`"`)
+  }
+}
+
+func main() {
+  gpu_type := "nvidia-tesla-p100-vws"
+  PZ := "--project=" + build_project + " --zone=" + build_zone
+
+  dest_family_flag := ""
+  if dest_family != "" {
+    dest_family_flag = "--family=" + dest_family
+  }
+
+  scratch_dir, err := ioutil.TempDir("", "")
+  if err != nil {
+    log.Fatal(err)
+  }
+
+  oldDir, err := os.Getwd()
+  if err != nil {
+    log.Fatal(err)
+  }
+  os.Chdir(scratch_dir)
+  packageSource(repository_url, repository_branch, "cuttlefish-common_" + version, "")
+  os.Chdir(oldDir)
+
+  abt := os.Getenv("ANDROID_BUILD_TOP")
+  source_files := `"` + abt + `/device/google/cuttlefish/tools/create_base_image_gce.sh"`
+  source_files += " " + `"` + abt + `/device/google/cuttlefish/tools/update_gce_kernel.sh"`
+  source_files += " " + `"` + abt + `/device/google/cuttlefish/tools/remove_old_gce_kernel.sh"`
+  source_files += " " + scratch_dir + "/*"
+  if INTERNAL_extra_source != "" {
+    source_files += " " + INTERNAL_extra_source + "/*"
+  }
+
+  delete_instances := build_instance + " " + dest_image
+  if launch_instance != "" {
+    delete_instances += " " + launch_instance
+  }
+
+  gce(WarnOnFail, `compute instances delete -q `+PZ+` `+delete_instances,
+    `Not running`)
+  gce(WarnOnFail, `compute disks delete -q `+PZ+` "`+dest_image+
+    `"`, `No scratch disk`)
+  gce(WarnOnFail, `compute images delete -q --project="`+build_project+
+    `" "`+dest_image+`"`, `Not respinning`)
+  gce(WarnOnFail, `compute disks create `+PZ+` --image-family="`+source_image_family+
+    `" --image-project="`+source_image_project+`" "`+dest_image+`"`)
+  gce(ExitOnFail, `compute accelerator-types describe "`+gpu_type+`" `+PZ,
+    `Please use a zone with `+gpu_type+` GPUs available.`)
+  createInstance(build_instance, PZ+
+    ` --machine-type=n1-standard-16 --image-family="`+source_image_family+
+    `" --image-project="`+source_image_project+
+    `" --boot-disk-size=200GiB --accelerator="type=`+gpu_type+
+    `,count=1" --maintenance-policy=TERMINATE --boot-disk-size=200GiB`)
+
+  waitForInstance(PZ)
+
+  // Ubuntu tends to mount the wrong disk as root, so help it by waiting until
+  // it has booted before giving it access to the clean image disk
+  gce(WarnOnFail, `compute instances attach-disk `+PZ+` "`+build_instance+
+    `" --disk="`+dest_image+`"`)
+
+  // beta for the --internal-ip flag that may be passed via SSH_FLAGS
+  gce(ExitOnFail, `beta compute scp `+SSH_FLAGS+` `+PZ+` `+source_files+
+    ` "`+build_instance+`:"`)
+
+  // Update the host kernel before installing any kernel modules
+  // Needed to guarantee that the modules in the chroot aren't built for the
+  // wrong kernel
+  gce(WarnOnFail, `compute ssh `+SSH_FLAGS+` `+PZ+` "`+build_instance+
+    `" -- ./update_gce_kernel.sh`)
+  // TODO rammuthiah if the instance is clobbered with ssh commands within
+  // 5 seconds of reboot, it becomes inaccessible. Workaround that by sleeping
+  // 50 seconds.
+  time.Sleep(50 * time.Second)
+  gce(ExitOnFail, `compute ssh `+SSH_FLAGS+` `+PZ+` "`+build_instance+
+    `" -- ./remove_old_gce_kernel.sh`)
+
+  gce(ExitOnFail, `compute ssh `+SSH_FLAGS+` `+PZ+` "`+build_instance+
+    `" -- ./create_base_image_gce.sh`)
+  gce(ExitOnFail, `compute instances delete -q `+PZ+` "`+build_instance+`"`)
+  gce(ExitOnFail, `compute images create --project="`+build_project+
+    `" --source-disk="`+dest_image+`" --source-disk-zone="`+build_zone+
+    `" --licenses=https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx `+
+    dest_family_flag+` "`+dest_image+`"`)
+  gce(ExitOnFail, `compute disks delete -q `+PZ+` "`+dest_image+`"`)
+
+  if launch_instance != "" {
+    createInstance(launch_instance, PZ+
+      ` --image-project="`+build_project+`" --image="`+dest_image+
+      `" --machine-type=n1-standard-4 --scopes storage-ro --accelerator="type=`+
+      gpu_type+`,count=1" --maintenance-policy=TERMINATE`)
+  }
+
+  fmt.Printf("Test and if this looks good, consider releasing it via:\n"+
+            "\n"+
+            "gcloud compute images create \\\n"+
+            "  --project=\"%s\" \\\n"+
+            "  --source-image=\"%s\" \\\n"+
+            "  --source-image-project=\"%s\" \\\n"+
+            "  \"%s\" \\\n"+
+            "  \"%s\"\n",
+            dest_project, dest_image, build_project, dest_family_flag, dest_image)
+}
diff --git a/tools/create_base_image.sh b/tools/create_base_image.sh
deleted file mode 100755
index bd85f0b..0000000
--- a/tools/create_base_image.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-
-# Creates a base image suitable for booting cuttlefish on GCE
-
-source "${ANDROID_BUILD_TOP}/device/google/cuttlefish/tools/create_base_image_hostlib.sh"
-
-FLAGS "$@" || exit 1
-main "${FLAGS_ARGV[@]}"
diff --git a/tools/create_base_image_arm.sh b/tools/create_base_image_arm.sh
index 644d909..5258636 100755
--- a/tools/create_base_image_arm.sh
+++ b/tools/create_base_image_arm.sh
@@ -14,8 +14,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+set -e
+set -u
+
 script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
 
+if [ -z $ANDROID_BUILD_TOP ]; then
+	echo "error: run script after 'lunch'"
+	exit 1
+fi
+
 source "${ANDROID_BUILD_TOP}/external/shflags/shflags"
 
 DEFINE_boolean p1 \
@@ -29,7 +37,11 @@
 DEFINE_boolean p5 \
 	false "Only generate/write the 5th partition (rootfs)" "5"
 
-FLAGS_HELP="USAGE: $0 <KERNEL_DIR> [IMAGE] [flags]"
+UBOOT_REPO=
+KERNEL_REPO=
+IMAGE=
+
+FLAGS_HELP="USAGE: $0 <UBOOT_REPO> <KERNEL_REPO> [IMAGE] [flags]"
 
 FLAGS "$@" || exit $?
 eval set -- "${FLAGS_ARGV}"
@@ -47,8 +59,10 @@
 fi
 
 for arg in "$@" ; do
-	if [ -z $KERNEL_DIR ]; then
-		KERNEL_DIR=$arg
+	if [ -z $UBOOT_REPO ]; then
+		UBOOT_REPO=$arg
+	elif [ -z $KERNEL_REPO ]; then
+		KERNEL_REPO=$arg
 	elif [ -z $IMAGE ]; then
 		IMAGE=$arg
 	else
@@ -59,25 +73,18 @@
 
 USE_IMAGE=`[ -z "${IMAGE}" ] && echo "0" || echo "1"`
 OVERWRITE=`[ -e "${IMAGE}" ] && echo "1" || echo "0"`
-if [ -z $KERNEL_DIR ]; then
+if [ -z $KERNEL_REPO -o -z $UBOOT_REPO ]; then
 	flags_help
 	exit 1
 fi
-if [ ! -e "${KERNEL_DIR}" ]; then
-	echo "error: can't find '${KERNEL_DIR}'. aborting..."
+if [ ! -e "${UBOOT_REPO}" ]; then
+	echo "error: can't find '${UBOOT_REPO}'. aborting..."
 	exit 1
 fi
-
-# escalate to superuser
-if [ $UID -ne 0 ]; then
-	cd ${ANDROID_BUILD_TOP}
-	. ./build/envsetup.sh
-	lunch ${TARGET_PRODUCT}-${TARGET_BUILD_VARIANT}
-	mmma external/u-boot
-	cd -
-	exec sudo -E "${0}" ${@}
+if [ ! -e "${KERNEL_REPO}" ]; then
+	echo "error: can't find '${KERNEL_REPO}'. aborting..."
+	exit 1
 fi
-
 if [ $OVERWRITE -eq 1 ]; then
 	OVERWRITE_IMAGE=${IMAGE}
 	IMAGE=`mktemp`
@@ -118,15 +125,6 @@
 	echo "Detected device at /dev/${mmc_dev}"
 fi
 
-if [ ${FLAGS_p1} -eq ${FLAGS_TRUE} ]; then
-	cd ${ANDROID_BUILD_TOP}/external/arm-trusted-firmware
-	CROSS_COMPILE=aarch64-linux-gnu- make PLAT=rk3399 DEBUG=0 ERROR_DEPRECATED=1 bl31
-	export BL31="${ANDROID_BUILD_TOP}/external/arm-trusted-firmware/build/rk3399/release/bl31/bl31.elf"
-	cd -
-fi
-
-cd ${ANDROID_BUILD_TOP}/external/u-boot
-
 if [ ${FLAGS_p2} -eq ${FLAGS_TRUE} ]; then
 	tmpfile=`mktemp`
 	bootenv=`mktemp`
@@ -144,474 +142,62 @@
 find_script=if test -e mmc ${devnum}:${distro_bootpart} /boot/boot.scr; then echo Found U-Boot script /boot/boot.scr; run run_scr; fi
 run_scr=load mmc ${devnum}:${distro_bootpart} ${scriptaddr} /boot/boot.scr; source ${scriptaddr}
 EOF
-	echo "Sha=`${script_dir}/gen_sha.sh --kernel ${KERNEL_DIR}`" >> ${tmpfile}
-	${ANDROID_HOST_OUT}/bin/mkenvimage -s 32768 -o ${bootenv} - < ${tmpfile}
+	echo "Sha=`${script_dir}/gen_sha.sh --uboot ${UBOOT_REPO} --kernel ${KERNEL_REPO}`" >> ${tmpfile}
+	${ANDROID_BUILD_TOP}/device/google/cuttlefish_prebuilts/uboot_tools/mkenvimage -s 32768 -o ${bootenv} - < ${tmpfile}
 fi
 
 if [ ${FLAGS_p1} -eq ${FLAGS_TRUE} ] || [ ${FLAGS_p3} -eq ${FLAGS_TRUE} ]; then
-	make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- rock-pi-4-rk3399_defconfig
-	if [ ${FLAGS_p1} -eq ${FLAGS_TRUE} ]; then
-		make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- -j`nproc`
-	fi
-	if [ ${FLAGS_p3} -eq ${FLAGS_TRUE} ]; then
-		make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- u-boot.itb
-	fi
-	if [ ${FLAGS_p1} -eq ${FLAGS_TRUE} ]; then
-		idbloader=`mktemp`
-		${ANDROID_HOST_OUT}/bin/mkimage -n rk3399 -T rksd -d tpl/u-boot-tpl.bin ${idbloader}
-		cat spl/u-boot-spl.bin >> ${idbloader}
-	fi
+	cd ${UBOOT_REPO}
+	BUILD_CONFIG=u-boot/build.config.rockpi4 build/build.sh -j1
+	cd -
 fi
-cd -
 
 if [ ${FLAGS_p5} -eq ${FLAGS_TRUE} ]; then
-	${ANDROID_BUILD_TOP}/kernel/tests/net/test/build_rootfs.sh -a arm64 -s buster -n ${IMAGE}
+	cd ${KERNEL_REPO}
+	rm -rf out
+	BUILD_CONFIG=common/build.config.rockpi4 build/build.sh -j`nproc`
+	cd -
+
+	dist_dir=$(echo ${KERNEL_REPO}/out/android*/dist)
+	${ANDROID_BUILD_TOP}/kernel/tests/net/test/build_rootfs.sh \
+		-a arm64 -s bullseye-rockpi -n ${IMAGE} -r ${IMAGE}.initrd -e \
+		-k ${dist_dir}/Image -i ${dist_dir}/initramfs.img \
+		-d ${dist_dir}/rk3399-rock-pi-4b.dtb:rockchip
 	if [ $? -ne 0 ]; then
 		echo "error: failed to build rootfs. exiting..."
 		exit 1
 	fi
+	rm -f ${IMAGE}.initrd
 	truncate -s +3G ${IMAGE}
 	e2fsck -f ${IMAGE}
 	resize2fs ${IMAGE}
 
-	mntdir=`mktemp -d`
-	mount ${IMAGE} ${mntdir}
-	if [ $? != 0 ]; then
-		echo "error: unable to mount ${IMAGE} ${mntdir}"
-		exit 1
-	fi
-
-	cat > ${mntdir}/boot/boot.cmd << "EOF"
-setenv start_poe 'gpio set 150; gpio clear 146'
-run start_poe
-setenv bootcmd_dhcp '
-mw.b ${scriptaddr} 0 0x8000
-mmc dev 0 0
-mmc read ${scriptaddr} 0x1fc0 0x40
-env import -b ${scriptaddr} 0x8000
-mw.b ${scriptaddr} 0 0x8000
-if dhcp ${scriptaddr} manifest.txt; then
-	setenv OldSha ${Sha}
-	setenv Sha
-	env import -t ${scriptaddr} 0x8000 ManifestVersion
-	echo "Manifest version $ManifestVersion";
-	if test "$ManifestVersion" = "1"; then
-		run manifest1
-	elif test "$ManifestVersion" = "2"; then
-		run manifest2
-	else
-		run manifestX
-	fi
-fi'
-setenv manifestX 'echo "***** ERROR: Unknown manifest version! *****";'
-setenv manifest1 '
-env import -t ${scriptaddr} 0x8000
-if test "$Sha" != "$OldSha"; then
-	setenv serverip ${TftpServer}
-	setenv loadaddr 0x00200000
-	mmc dev 0 0;
-	setenv file $TplSplImg; offset=0x40; size=0x1f80; run tftpget1; setenv TplSplImg
-	setenv file $UbootItb;  offset=0x4000; size=0x2000; run tftpget1; setenv UbootItb
-	setenv file $TrustImg; offset=0x6000; size=0x2000; run tftpget1; setenv TrustImg
-	setenv file $RootfsImg; offset=0x8000; size=0; run tftpget1; setenv RootfsImg
-	setenv file $UbootEnv; offset=0x1fc0; size=0x40; run tftpget1; setenv UbootEnv
-	mw.b ${scriptaddr} 0 0x8000
-	env export -b ${scriptaddr} 0x8000
-	mmc write ${scriptaddr} 0x1fc0 0x40
-else
-	echo "Already have ${Sha}. Booting..."
-fi'
-setenv manifest2 '
-env import -t ${scriptaddr} 0x8000
-if test "$DFUethaddr" = "$ethaddr" || test "$DFUethaddr" = ""; then
-	if test "$Sha" != "$OldSha"; then
-		setenv serverip ${TftpServer}
-		setenv loadaddr 0x00200000
-		mmc dev 0 0;
-		setenv file $TplSplImg; offset=0x40; size=0x1f80; run tftpget1; setenv TplSplImg
-		setenv file $UbootItb;  offset=0x4000; size=0x2000; run tftpget1; setenv UbootItb
-		setenv file $TrustImg; offset=0x6000; size=0x2000; run tftpget1; setenv TrustImg
-		setenv file $RootfsImg; offset=0x8000; size=0; run tftpget1; setenv RootfsImg
-		setenv file $UbootEnv; offset=0x1fc0; size=0x40; run tftpget1; setenv UbootEnv
-		mw.b ${scriptaddr} 0 0x8000
-		env export -b ${scriptaddr} 0x8000
-		mmc write ${scriptaddr} 0x1fc0 0x40
-	else
-		echo "Already have ${Sha}. Booting..."
-	fi
-else
-	echo "Update ${Sha} is not for me. Booting..."
-fi'
-setenv tftpget1 '
-if test "$file" != ""; then
-	mw.b ${loadaddr} 0 0x400000
-	tftp ${file}
-	if test $? = 0; then
-		setenv isGz 0 && setexpr isGz sub .*\\.gz\$ 1 ${file}
-		if test $isGz = 1; then
-			if test ${file} = ${UbootEnv}; then
-				echo "** gzipped env unsupported **"
-			else
-				setexpr boffset ${offset} * 0x200
-				gzwrite mmc 0 ${loadaddr} 0x${filesize} 100000 ${boffset} && echo Updated: ${file}
-			fi
-		elif test ${file} = ${UbootEnv}; then
-			env import -b ${loadaddr} && echo Updated: ${file}
-		else
-			if test $size = 0; then
-				setexpr x $filesize - 1
-				setexpr x $x / 0x1000
-				setexpr x $x + 1
-				setexpr x $x * 0x1000
-				setexpr x $x / 0x200
-				size=0x${x}
-			fi
-			mmc write ${loadaddr} ${offset} ${size} && echo Updated: ${file}
-		fi
-	fi
-	if test $? != 0; then
-		echo ** UPDATE FAILED: ${file} **
-	fi
-fi'
-if mmc dev 1 0; then; else
-	run bootcmd_dhcp;
-fi
-load mmc ${devnum}:${distro_bootpart} 0x02080000 /boot/Image
-load mmc ${devnum}:${distro_bootpart} 0x04000000 /boot/uInitrd
-load mmc ${devnum}:${distro_bootpart} 0x01f00000 /boot/dtb/rockchip/rk3399-rock-pi-4.dtb
-setenv finduuid "part uuid mmc ${devnum}:${distro_bootpart} uuid"
-run finduuid
-setenv bootargs "earlycon=uart8250,mmio32,0xff1a0000 console=ttyS2,1500000n8 loglevel=7 root=PARTUUID=${uuid} rootwait rootfstype=ext4 sdhci.debug_quirks=0x20000000 of_devlink=0"
-booti 0x02080000 0x04000000 0x01f00000
-EOF
-	${ANDROID_HOST_OUT}/bin/mkimage \
-		-C none -A arm -T script -d ${mntdir}/boot/boot.cmd ${mntdir}/boot/boot.scr
-
-	cd ${KERNEL_DIR}
-	export PATH=${ANDROID_BUILD_TOP}/prebuilts/clang/host/linux-x86/clang-r353983c/bin:$PATH
-	export PATH=${ANDROID_BUILD_TOP}/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin:$PATH
-	make ARCH=arm64 CC=clang CROSS_COMPILE=aarch64-linux-androidkernel- \
-	      CLANG_TRIPLE=aarch64-linux-gnu- rockpi4_defconfig
-	make ARCH=arm64 CC=clang CROSS_COMPILE=aarch64-linux-androidkernel- \
-	      CLANG_TRIPLE=aarch64-linux-gnu- -j`nproc`
-
-	cp ${KERNEL_DIR}/arch/arm64/boot/Image ${mntdir}/boot/
-	mkdir -p ${mntdir}/boot/dtb/rockchip/
-	cp ${KERNEL_DIR}/arch/arm64/boot/dts/rockchip/rk3399-rock-pi-4.dtb ${mntdir}/boot/dtb/rockchip/
-	cd -
-
-	mount -o bind /proc ${mntdir}/proc
-	mount -o bind /sys ${mntdir}/sys
-	mount -o bind /dev ${mntdir}/dev
-
-	echo "Installing required packages..."
-	chroot ${mntdir} /bin/bash <<EOF
-apt-get update
-apt-get install -y -f initramfs-tools u-boot-tools network-manager openssh-server sudo man-db vim git dpkg-dev cdbs debhelper config-package-dev gdisk eject lzop binfmt-support ntpdate lsof
-EOF
-
-	echo "Turning on DHCP client..."
-	cat >${mntdir}/etc/systemd/network/dhcp.network <<EOF
-[Match]
-Name=en*
-
-[Network]
-DHCP=yes
-EOF
-
-	chroot ${mntdir} /bin/bash << "EOT"
-echo "Adding user vsoc-01 and groups..."
-useradd -m -G kvm,sudo -d /home/vsoc-01 --shell /bin/bash vsoc-01
-echo -e "cuttlefish\ncuttlefish" | passwd
-echo -e "cuttlefish\ncuttlefish" | passwd vsoc-01
-EOT
-
-	echo "Cloning android-cuttlefish..."
-	cd ${mntdir}/home/vsoc-01
-	git clone https://github.com/google/android-cuttlefish.git
-	cd -
-
-	echo "Creating PoE script..."
-	cat > ${mntdir}/usr/local/bin/poe << "EOF"
-#!/bin/bash
-
-if [ "$1" == "--start" ]; then
-	echo 146 > /sys/class/gpio/export
-	echo out > /sys/class/gpio/gpio146/direction
-	echo 0 > /sys/class/gpio/gpio146/value
-	echo 150 > /sys/class/gpio/export
-	echo out > /sys/class/gpio/gpio150/direction
-	echo 1 > /sys/class/gpio/gpio150/value
-	exit 0
-fi
-
-if [ "$1" == "--stop" ]; then
-	echo 0 > /sys/class/gpio/gpio146/value
-	echo 146 > /sys/class/gpio/unexport
-	echo 0 > /sys/class/gpio/gpio150/value
-	echo 150 > /sys/class/gpio/unexport
-	exit 0
-fi
-
-if [ ! -e /sys/class/gpio/gpio146/value ] || [ ! -e /sys/class/gpio/gpio150/value ]; then
-	echo "error: PoE service not initialized"
-	exit 1
-fi
-
-if [ "$1" == "0" ] || [ "$1" == "off" ] || [ "$1" == "OFF" ]; then
-	echo 0 > /sys/class/gpio/gpio150/value
-	exit 0
-fi
-
-if [ "$1" == "1" ] || [ "$1" == "on" ] || [ "$1" == "ON" ]; then
-	echo 1 > /sys/class/gpio/gpio150/value
-	exit 0
-fi
-
-echo "usage: poe <0|1>"
-exit 1
-EOF
-	chown root:root ${mntdir}/usr/local/bin/poe
-	chmod 755 ${mntdir}/usr/local/bin/poe
-
-	echo "Creating PoE service..."
-	cat > ${mntdir}/etc/systemd/system/poe.service << EOF
-[Unit]
- Description=PoE service
- ConditionPathExists=/usr/local/bin/poe
-
-[Service]
- Type=oneshot
- ExecStart=/usr/local/bin/poe --start
- ExecStop=/usr/local/bin/poe --stop
- RemainAfterExit=true
- StandardOutput=journal
-
-[Install]
- WantedBy=multi-user.target
-EOF
-
-	echo "Creating led script..."
-	cat > ${mntdir}/usr/local/bin/led << "EOF"
-#!/bin/bash
-
-if [ "$1" == "--start" ]; then
-	echo 125 > /sys/class/gpio/export
-	echo out > /sys/class/gpio/gpio125/direction
-	chmod 666 /sys/class/gpio/gpio125/value
-	echo 0 > /sys/class/gpio/gpio125/value
-	exit 0
-fi
-
-if [ "$1" == "--stop" ]; then
-	echo 0 > /sys/class/gpio/gpio125/value
-	echo 125 > /sys/class/gpio/unexport
-	exit 0
-fi
-
-if [ ! -e /sys/class/gpio/gpio125/value ]; then
-	echo "error: led service not initialized"
-	exit 1
-fi
-
-if [ "$1" == "0" ] || [ "$1" == "off" ] || [ "$1" == "OFF" ]; then
-	echo 0 > /sys/class/gpio/gpio125/value
-	exit 0
-fi
-
-if [ "$1" == "1" ] || [ "$1" == "on" ] || [ "$1" == "ON" ]; then
-	echo 1 > /sys/class/gpio/gpio125/value
-	exit 0
-fi
-
-echo "usage: led <0|1>"
-exit 1
-EOF
-	chown root:root ${mntdir}/usr/local/bin/led
-	chmod 755 ${mntdir}/usr/local/bin/led
-
-	echo "Creating led service..."
-	cat > ${mntdir}/etc/systemd/system/led.service << EOF
-[Unit]
- Description=led service
- ConditionPathExists=/usr/local/bin/led
-
-[Service]
- Type=oneshot
- ExecStart=/usr/local/bin/led --start
- ExecStop=/usr/local/bin/led --stop
- RemainAfterExit=true
- StandardOutput=journal
-
-[Install]
- WantedBy=multi-user.target
-EOF
-
-	echo "Creating SD duplicator script..."
-	cat > ${mntdir}/usr/local/bin/sd-dupe << "EOF"
-#!/bin/bash
-led 0
-
-src_dev=mmcblk0
-dest_dev=mmcblk1
-part_num=p5
-
-if [ -e /dev/mmcblk0p5 ] && [ -e /dev/mmcblk1p5 ]; then
-	led 1
-
-	sgdisk -Z -a1 /dev/${dest_dev}
-	sgdisk -a1 -n:1:64:8127 -t:1:8301 -c:1:loader1 /dev/${dest_dev}
-	sgdisk -a1 -n:2:8128:8191 -t:2:8301 -c:2:env /dev/${dest_dev}
-	sgdisk -a1 -n:3:16384:24575 -t:3:8301 -c:3:loader2 /dev/${dest_dev}
-	sgdisk -a1 -n:4:24576:32767 -t:4:8301 -c:4:trust /dev/${dest_dev}
-	sgdisk -a1 -n:5:32768:- -A:5:set:2 -t:5:8305 -c:5:rootfs /dev/${dest_dev}
-
-	src_block_count=`tune2fs -l /dev/${src_dev}${part_num} | grep "Block count:" | sed 's/.*: *//'`
-	src_block_size=`tune2fs -l /dev/${src_dev}${part_num} | grep "Block size:" | sed 's/.*: *//'`
-	src_fs_size=$(( src_block_count*src_block_size ))
-	src_fs_size_m=$(( src_fs_size / 1024 / 1024 + 1 ))
-
-	dd if=/dev/${src_dev}p1 of=/dev/${dest_dev}p1 conv=sync,noerror status=progress
-	dd if=/dev/${src_dev}p2 of=/dev/${dest_dev}p2 conv=sync,noerror status=progress
-	dd if=/dev/${src_dev}p3 of=/dev/${dest_dev}p3 conv=sync,noerror status=progress
-	dd if=/dev/${src_dev}p4 of=/dev/${dest_dev}p4 conv=sync,noerror status=progress
-
-	echo "Writing ${src_fs_size_m} MB: /dev/${src_dev} -> /dev/${dest_dev}..."
-	dd if=/dev/${src_dev}${part_num} of=/dev/${dest_dev}${part_num} bs=1M conv=sync,noerror status=progress
-
-	echo "Expanding /dev/${dest_dev}${part_num} filesystem..."
-	e2fsck -fy /dev/${dest_dev}${part_num}
-	resize2fs /dev/${dest_dev}${part_num}
-	tune2fs -O has_journal /dev/${dest_dev}${part_num}
-	e2fsck -fy /dev/${dest_dev}${part_num}
-	sync /dev/${dest_dev}
-
-	echo "Cleaning up..."
-	mount /dev/${dest_dev}${part_num} /media
-	chroot /media /usr/local/bin/install-cleanup
-
-	if [ $? == 0 ]; then
-		echo "Successfully copied Rock Pi image!"
-		while true; do
-			led 1; sleep 0.5
-			led 0; sleep 0.5
-		done
-	else
-		echo "Error while copying Rock Pi image"
-		while true; do
-			led 1; sleep 0.1
-			led 0; sleep 0.1
-		done
-	fi
-else
-	echo "Expanding /dev/${dest_dev}${part_num} filesystem..."
-	e2fsck -fy /dev/${dest_dev}${part_num}
-	resize2fs /dev/${dest_dev}${part_num}
-	tune2fs -O has_journal /dev/${dest_dev}${part_num}
-	e2fsck -fy /dev/${dest_dev}${part_num}
-	sync /dev/${dest_dev}
-
-	echo "Cleaning up..."
-	/usr/local/bin/install-cleanup
-fi
-EOF
-	chmod +x ${mntdir}/usr/local/bin/sd-dupe
-
-	echo "Creating SD duplicator service..."
-	cat > ${mntdir}/etc/systemd/system/sd-dupe.service << EOF
-[Unit]
- Description=Duplicate SD card rootfs to eMMC on Rock Pi
- ConditionPathExists=/usr/local/bin/sd-dupe
- After=led.service
-
-[Service]
- Type=simple
- ExecStart=/usr/local/bin/sd-dupe
- TimeoutSec=0
- StandardOutput=tty
-
-[Install]
- WantedBy=multi-user.target
-EOF
-
-	umount ${mntdir}/sys
-	umount ${mntdir}/dev
-	umount ${mntdir}/proc
-
-	chroot ${mntdir} /bin/bash << "EOT"
-echo "Installing cuttlefish-common package..."
-dpkg --add-architecture amd64
-apt-get update
-apt-get install -y -f libc6:amd64 qemu-user-static
-cd /home/vsoc-01/android-cuttlefish
-dpkg-buildpackage -d -uc -us
-apt-get install -y -f ../cuttlefish-common_*_arm64.deb
-apt-get clean
-
-usermod -aG cvdnetwork vsoc-01
-chmod 660 /dev/vhost-vsock
-chown root:cvdnetwork /dev/vhost-vsock
-rm -rf /home/vsoc-01/*
-EOT
-
-	echo "Creating cleanup script..."
-	cat > ${mntdir}/usr/local/bin/install-cleanup << "EOF"
-#!/bin/bash
-echo "nameserver 8.8.8.8" > /etc/resolv.conf
-MAC=`ip link | grep eth0 -A1 | grep ether | sed 's/.*\(..:..:..:..:..:..\) .*/\1/' | tr -d :`
-sed -i " 1 s/.*/& rockpi-${MAC}/" /etc/hosts
-sudo hostnamectl set-hostname "rockpi-${MAC}"
-
-rm /etc/machine-id
-rm /var/lib/dbus/machine-id
-dbus-uuidgen --ensure
-systemd-machine-id-setup
-
-systemctl disable sd-dupe
-rm /etc/systemd/system/sd-dupe.service
-rm /usr/local/bin/sd-dupe
-rm /usr/local/bin/install-cleanup
-EOF
-	chmod +x ${mntdir}/usr/local/bin/install-cleanup
-
-	chroot ${mntdir} /bin/bash << "EOT"
-echo "Enabling services..."
-systemctl enable poe
-systemctl enable led
-systemctl enable sd-dupe
-
-echo "Creating Initial Ramdisk..."
-update-initramfs -c -t -k "5.2.0"
-mkimage -A arm -O linux -T ramdisk -C none -a 0 -e 0 -n uInitrd -d /boot/initrd.img-5.2.0 /boot/uInitrd-5.2.0
-ln -s /boot/uInitrd-5.2.0 /boot/uInitrd
-EOT
-
-	umount ${mntdir}
-
 	# Turn on journaling
 	tune2fs -O ^has_journal ${IMAGE}
 	e2fsck -fy ${IMAGE} >/dev/null 2>&1
 fi
 
 if [ ${USE_IMAGE} -eq 0 ]; then
-	# 32GB eMMC size
-	end_sector=61071326
 	device=/dev/${mmc_dev}
 	devicep=${device}
 
-	sgdisk -Z -a1 ${device}
-	sgdisk -a1 -n:1:64:8127 -t:1:8301 -c:1:loader1 ${device}
-	sgdisk -a1 -n:2:8128:8191 -t:2:8301 -c:2:env ${device}
-	sgdisk -a1 -n:3:16384:24575 -t:3:8301 -c:3:loader2 ${device}
-	sgdisk -a1 -n:4:24576:32767 -t:4:8301 -c:4:trust ${device}
-	sgdisk -a1 -n:5:32768:${end_sector} -A:5:set:2 -t:5:8305 -c:5:rootfs ${device}
+	# 32GB eMMC size
+	end_sector=61071326
+
+	sudo sgdisk --zap-all --set-alignment=1 ${device}
+	sudo sgdisk --set-alignment=1 --new=1:64:8127 --typecode=1:8301 --change-name=1:loader1 ${device}
+	sudo sgdisk --set-alignment=1 --new=2:8128:8191 --typecode=2:8301 --change-name=2:env ${device}
+	sudo sgdisk --set-alignment=1 --new=3:16384:24575 --typecode=3:8301 --change-name=3:loader2 ${device}
+	sudo sgdisk --set-alignment=1 --new=4:24576:32767 --typecode=4:8301 --change-name=4:trust ${device}
+	sudo sgdisk --set-alignment=1 --new=5:32768:${end_sector} --typecode=5:8305 --change-name=5:rootfs --attributes=5:set:2 ${device}
 	if [ ${FLAGS_p5} -eq ${FLAGS_TRUE} ]; then
-		dd if=${IMAGE} of=${devicep}5 bs=1M
-		resize2fs ${devicep}5 >/dev/null 2>&1
+		sudo dd if=${IMAGE} of=${devicep}5 bs=1M conv=fsync
+		sudo resize2fs ${devicep}5 >/dev/null 2>&1
 	fi
 else
-	device=$(losetup -f)
+	device=$(sudo losetup -f)
 	devicep=${device}p
+
 	if [ ${FLAGS_p5} -eq ${FLAGS_FALSE} ]; then
 		fs_end=3G
 		end_sector=-
@@ -649,34 +235,37 @@
 	truncate -s ${fs_end} ${tmpimg}
 
 	# Create GPT
-	sgdisk -Z -a1 ${tmpimg}
-	sgdisk -a1 -n:1:64:8127 -t:1:8301 -c:1:loader1 ${tmpimg}
-	sgdisk -a1 -n:2:8128:8191 -t:2:8301 -c:2:env ${tmpimg}
-	sgdisk -a1 -n:3:16384:24575 -t:3:8301 -c:3:loader2 ${tmpimg}
-	sgdisk -a1 -n:4:24576:32767 -t:4:8301 -c:4:trust ${tmpimg}
-	sgdisk -a1 -n:5:32768:${end_sector} -A:5:set:2 -t:5:8305 -c:5:rootfs ${tmpimg}
+	sgdisk --zap-all --set-alignment=1 ${tmpimg}
+	sgdisk --set-alignment=1 --new=1:64:8127 --typecode=1:8301 --change-name=1:loader1 ${tmpimg}
+	sgdisk --set-alignment=1 --new=2:8128:8191 --typecode=2:8301 --change-name=2:env ${tmpimg}
+	sgdisk --set-alignment=1 --new=3:16384:24575 --typecode=3:8301 --change-name=3:loader2 ${tmpimg}
+	sgdisk --set-alignment=1 --new=4:24576:32767 --typecode=4:8301 --change-name=4:trust ${tmpimg}
+	sgdisk --set-alignment=1 --new=5:32768:${end_sector} --typecode=5:8305 --change-name=5:rootfs --attributes=5:set:2 ${tmpimg}
 
-	losetup ${device} ${tmpimg}
-	partx -v --add ${device}
+	sudo losetup ${device} ${tmpimg}
+	sudo partx -v --add ${device}
 
 	if [ ${FLAGS_p5} -eq ${FLAGS_TRUE} ]; then
-		dd if=${IMAGE} of=${devicep}5 bs=1M
+		sudo dd if=${IMAGE} of=${devicep}5 bs=1M conv=fsync
 	fi
 fi
 if [ ${FLAGS_p1} -eq ${FLAGS_TRUE} ]; then
-	dd if=${idbloader} of=${devicep}1
+	# sudo dd if=${UBOOT_REPO}/out/u-boot-mainline/dist/idbloader.img of=${devicep}1 conv=fsync
+	# loader1
+	sudo dd if=${ANDROID_BUILD_TOP}/device/google/cuttlefish_prebuilts/uboot_bin/idbloader.img of=${devicep}1 conv=fsync
 fi
 if [ ${FLAGS_p2} -eq ${FLAGS_TRUE} ]; then
-	dd if=${bootenv} of=${devicep}2
+	sudo dd if=${bootenv} of=${devicep}2 conv=fsync
 fi
 if [ ${FLAGS_p3} -eq ${FLAGS_TRUE} ]; then
-	dd if=${ANDROID_BUILD_TOP}/external/u-boot/u-boot.itb of=${devicep}3
+	# sudo dd if=${UBOOT_REPO}/out/u-boot-mainline/dist/u-boot.itb of=${devicep}3 conv=fsync
+	# loader2
+	sudo dd if=${ANDROID_BUILD_TOP}/device/google/cuttlefish_prebuilts/uboot_bin/u-boot.itb of=${devicep}3 conv=fsync
 fi
 if [ ${USE_IMAGE} -eq 1 ]; then
-	chown $SUDO_USER:`id -ng $SUDO_USER` ${tmpimg}
+	sudo partx -v --delete ${device}
+	sudo losetup -d ${device}
 	if [ $OVERWRITE -eq 0 ]; then
 		mv ${tmpimg} ${IMAGE}
 	fi
-	partx -v --delete ${device}
-	losetup -d ${device}
 fi
diff --git a/tools/create_base_image_gce.sh b/tools/create_base_image_gce.sh
index 02797db..8a3014d 100755
--- a/tools/create_base_image_gce.sh
+++ b/tools/create_base_image_gce.sh
@@ -39,7 +39,7 @@
 done
 
 # Now install the packages on the disk
-sudo mkdir /mnt/image
+sudo mkdir -p /mnt/image
 sudo mount /dev/sdb1 /mnt/image
 cp "${debs[@]}" /mnt/image/tmp
 sudo mount -t sysfs none /mnt/image/sys
@@ -59,31 +59,30 @@
 sudo chroot /mnt/image /usr/bin/apt install -y screen # needed by tradefed
 
 sudo chroot /mnt/image /usr/bin/find /home -ls
+sudo chroot /mnt/image /usr/bin/apt install -t bullseye-backports -y linux-image-cloud-amd64
+sudo chroot /mnt/image /usr/bin/apt --purge -y remove linux-image-5.10.0-10-cloud-amd64
 
+# update QEMU version to most recent backport
+sudo chroot /mnt/image /usr/bin/apt install -y --only-upgrade qemu-system-x86 -t bullseye-backports
+sudo chroot /mnt/image /usr/bin/apt install -y --only-upgrade qemu-system-arm -t bullseye-backports
 
 # Install GPU driver dependencies
 sudo chroot /mnt/image /usr/bin/apt install -y gcc
 sudo chroot /mnt/image /usr/bin/apt install -y linux-source
 sudo chroot /mnt/image /usr/bin/apt install -y linux-headers-`uname -r`
 sudo chroot /mnt/image /usr/bin/apt install -y make
+sudo chroot /mnt/image /usr/bin/apt install -y software-properties-common
+sudo chroot /mnt/image /usr/bin/add-apt-repository non-free
+sudo chroot /mnt/image /usr/bin/add-apt-repository contrib
+# TODO rammuthiah rootcause why this line is needed
+# For reasons unknown the above two lines don't add non-free and
+# contrib to the bullseye backports.
+sudo chroot /mnt/image /usr/bin/add-apt-repository 'deb http://deb.debian.org/debian bullseye-backports main non-free contrib'
+sudo chroot /mnt/image /usr/bin/apt update
 
-# Download the latest GPU driver installer
-gsutil cp \
-  $(gsutil ls gs://nvidia-drivers-us-public/GRID/GRID*/*-Linux-x86_64-*.run \
-    | sort \
-    | tail -n 1) \
-  /mnt/image/tmp/nvidia-driver-installer.run
-
-# Make GPU driver installer executable
-chmod +x /mnt/image/tmp/nvidia-driver-installer.run
-
-# Install the latest GPU driver with default options and the dispatch libs
-sudo chroot /mnt/image /tmp/nvidia-driver-installer.run \
-  --silent \
-  --install-libglvnd
-
-# Cleanup after install
-rm /mnt/image/tmp/nvidia-driver-installer.run
+sudo chroot /mnt/image /bin/bash -c 'DEBIAN_FRONTEND=noninteractive /usr/bin/apt install -y nvidia-driver -t bullseye-backports'
+sudo chroot /mnt/image /usr/bin/apt install -y firmware-misc-nonfree -t bullseye-backports
+sudo chroot /mnt/image /usr/bin/apt install -y libglvnd-dev -t bullseye-backports
 
 # Verify
 query_nvidia() {
@@ -101,11 +100,11 @@
 fi
 
 # Vulkan loader
-sudo chroot /mnt/image /usr/bin/apt install -y libvulkan1
+sudo chroot /mnt/image /usr/bin/apt install -y libvulkan1 -t bullseye-backports
 
 # Wayland-server needed to have Nvidia driver fail gracefully when attemping to
 # use the EGL API on GCE instances without a GPU.
-sudo chroot /mnt/image /usr/bin/apt install -y libwayland-server0
+sudo chroot /mnt/image /usr/bin/apt install -y libwayland-server0 -t bullseye-backports
 
 # Clean up the builder's version of resolv.conf
 sudo rm /mnt/image/etc/resolv.conf
diff --git a/tools/create_base_image_hostlib.sh b/tools/create_base_image_hostlib.sh
index dafefbd..cc7227f 100755
--- a/tools/create_base_image_hostlib.sh
+++ b/tools/create_base_image_hostlib.sh
@@ -2,9 +2,6 @@
 
 # Common code to build a host image on GCE
 
-# INTERNAL_extra_source may be set to a directory containing the source for
-# extra package to build.
-
 # INTERNAL_IP can be set to --internal-ip run on a GCE instance
 # The instance will need --scope compute-rw
 
@@ -22,7 +19,7 @@
   "Project to use for the new image" "p"
 DEFINE_string launch_instance "" \
   "Name of the instance to launch with the new image" "l"
-DEFINE_string source_image_family "debian-10" \
+DEFINE_string source_image_family "debian-11" \
   "Image familty to use as the base" "s"
 DEFINE_string source_image_project debian-cloud \
   "Project holding the base image" "m"
@@ -79,9 +76,6 @@
     "${ANDROID_BUILD_TOP}/device/google/cuttlefish/tools/create_base_image_gce.sh"
     ${scratch_dir}/*
   )
-  if [[ -n "${INTERNAL_extra_source}" ]]; then
-    source_files+=("${INTERNAL_extra_source}"/*)
-  fi
 
   delete_instances=("${FLAGS_build_instance}" "${FLAGS_dest_image}")
   if [[ -n "${FLAGS_launch_instance}" ]]; then
@@ -143,6 +137,7 @@
       --scopes storage-ro \
       --accelerator="type=${gpu_type},count=1" \
       --maintenance-policy=TERMINATE \
+      --boot-disk-size=200GiB \
       "${FLAGS_launch_instance}"
   fi
   cat <<EOF
@@ -156,3 +151,11 @@
       "${FLAGS_dest_image}"
 EOF
 }
+
+FLAGS "$@" || exit 1
+if [[ "${FLAGS_help}" -eq 0 ]]; then
+  echo ${FLAGS_help}
+  exit 1
+fi
+
+main "${FLAGS_ARGV[@]}"
diff --git a/tools/gen_sha.sh b/tools/gen_sha.sh
index 51dc76c..d3da1c4 100755
--- a/tools/gen_sha.sh
+++ b/tools/gen_sha.sh
@@ -14,17 +14,22 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-source "${ANDROID_BUILD_TOP}/external/shflags/src/shflags"
+set -e
+set -u
+
+source "${ANDROID_BUILD_TOP}/external/shflags/shflags"
 
 DEFINE_string kernel \
-  "" "Path to kernel build dir" "k"
+  "" "Path to kernel repo checkout" "k"
+DEFINE_string uboot \
+  "" "Path to u-boot repo checkout" "u"
 
 FLAGS_HELP="USAGE: $0 [flags]"
 
 FLAGS "$@" || exit $?
 eval set -- "${FLAGS_ARGV}"
 
-if [ -z ${FLAGS_kernel} ]; then
+if [ -z ${FLAGS_kernel} -o -z ${FLAGS_uboot} ]; then
 	flags_help
 	exit 1
 fi
@@ -32,13 +37,14 @@
 cd "${ANDROID_BUILD_TOP}/device/google/cuttlefish"
 Sha=`git rev-parse HEAD`
 cd - >/dev/null
-cd "${ANDROID_BUILD_TOP}/external/u-boot"
+# cd "${FLAGS_uboot}/u-boot"
+cd "${ANDROID_BUILD_TOP}/device/google/cuttlefish_prebuilts"
 Sha="$Sha,`git rev-parse HEAD`"
 cd - >/dev/null
-cd "${ANDROID_BUILD_TOP}/external/arm-trusted-firmware"
+cd "${FLAGS_uboot}/external/arm-trusted-firmware"
 Sha="$Sha,`git rev-parse HEAD`"
 cd - >/dev/null
-cd "${FLAGS_kernel}"
+cd "${FLAGS_kernel}/common"
 Sha="$Sha,`git rev-parse HEAD`"
 cd - >/dev/null
 echo $Sha
diff --git a/tools/go.mod b/tools/go.mod
new file mode 100644
index 0000000..00809b9
--- /dev/null
+++ b/tools/go.mod
@@ -0,0 +1,3 @@
+module tools
+
+go 1.17
diff --git a/tools/latest_fetch_cvd.sh b/tools/latest_fetch_cvd.sh
new file mode 100755
index 0000000..d853786
--- /dev/null
+++ b/tools/latest_fetch_cvd.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+LATEST_BUILD_ID=`curl "https://www.googleapis.com/android/internal/build/v3/builds?branch=aosp-master&buildAttemptStatus=complete&buildType=submitted&maxResults=1&successful=true&target=aosp_cf_x86_64_phone-userdebug" 2>/dev/null | \
+  python3 -c "import sys, json; print(json.load(sys.stdin)['builds'][0]['buildId'])"`
+LATEST_BUILD_URL=`curl "https://www.googleapis.com/android/internal/build/v3/builds/$LATEST_BUILD_ID/aosp_cf_x86_64_phone-userdebug/attempts/latest/artifacts/fetch_cvd/url" 2>/dev/null | \
+  python3 -c "import sys, json; print(json.load(sys.stdin)['signedUrl'])"`
+
+DOWNLOAD_TARGET=`mktemp`
+
+curl "${LATEST_BUILD_URL}" -o $DOWNLOAD_TARGET
+
+chmod +x $DOWNLOAD_TARGET
+
+exec $DOWNLOAD_TARGET $@
diff --git a/tools/remove_old_gce_kernel.sh b/tools/remove_old_gce_kernel.sh
new file mode 100755
index 0000000..c7d52a1
--- /dev/null
+++ b/tools/remove_old_gce_kernel.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -x
+set -o errexit
+
+sudo apt --purge -y remove linux-image-5.10.0-10-cloud-amd64
+sudo update-grub2
diff --git a/tools/update_gce_kernel.sh b/tools/update_gce_kernel.sh
new file mode 100755
index 0000000..d77cc3c
--- /dev/null
+++ b/tools/update_gce_kernel.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -x
+set -o errexit
+
+sudo apt install -t bullseye-backports -y linux-image-cloud-amd64
+sudo reboot
diff --git a/tools/upload_to_gce_and_run.py b/tools/upload_to_gce_and_run.py
index f4dc4b8..db78340 100755
--- a/tools/upload_to_gce_and_run.py
+++ b/tools/upload_to_gce_and_run.py
@@ -59,9 +59,6 @@
 
 
 def __get_default_hostdir():
-  soong_host_dir = os.environ.get('ANDROID_SOONG_HOST_OUT')
-  if soong_host_dir:
-    return soong_host_dir
   return os.environ.get('ANDROID_HOST_OUT', '.')
 
 
diff --git a/tools/upload_via_ssh.py b/tools/upload_via_ssh.py
index 5359473..2b5cfd1 100755
--- a/tools/upload_via_ssh.py
+++ b/tools/upload_via_ssh.py
@@ -58,7 +58,7 @@
   parser.add_argument(
       '-host_dir',
       type=str,
-      default=os.environ.get('ANDROID_SOONG_HOST_OUT', '.'),
+      default=os.environ.get('ANDROID_HOST_OUT', '.'),
       help='path to soong host out directory')
   parser.add_argument(
       '-image_dir',
diff --git a/vsoc_arm64/auto/aosp_cf.mk b/vsoc_arm64/auto/aosp_cf.mk
deleted file mode 100644
index 51ae7a3..0000000
--- a/vsoc_arm64/auto/aosp_cf.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
-$(call inherit-product, device/google/cuttlefish/shared/auto/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_arm64/kernel.mk)
-
-PRODUCT_NAME := aosp_cf_arm64_auto
-PRODUCT_DEVICE := vsoc_arm64
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish arm64 auto
-
-PRODUCT_VENDOR_PROPERTIES += \
-    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
-    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_arm64/bootloader.mk b/vsoc_arm64/bootloader.mk
index a5aea94..ce29443 100644
--- a/vsoc_arm64/bootloader.mk
+++ b/vsoc_arm64/bootloader.mk
@@ -18,5 +18,3 @@
 # FIXME: Copying the QEMU bootloader for now, but this should be updated..
 BOARD_PREBUILT_BOOTLOADER := \
     device/google/cuttlefish_prebuilts/bootloader/crosvm_aarch64/u-boot.bin
-PRODUCT_COPY_FILES += \
-    device/google/cuttlefish_prebuilts/bootloader/qemu_aarch64/u-boot.bin:bootloader.qemu
diff --git a/vsoc_arm64/kernel.mk b/vsoc_arm64/kernel.mk
index 19d1b7c..dca198b 100644
--- a/vsoc_arm64/kernel.mk
+++ b/vsoc_arm64/kernel.mk
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-TARGET_KERNEL_USE ?= 5.10
+TARGET_KERNEL_USE ?= 5.15
 TARGET_KERNEL_PATH ?= kernel/prebuilts/$(TARGET_KERNEL_USE)/arm64/kernel-$(TARGET_KERNEL_USE)
 
 PRODUCT_COPY_FILES += $(TARGET_KERNEL_PATH):kernel
diff --git a/vsoc_arm64/phone/aosp_cf.mk b/vsoc_arm64/phone/aosp_cf.mk
index b33e523..86fe29e 100644
--- a/vsoc_arm64/phone/aosp_cf.mk
+++ b/vsoc_arm64/phone/aosp_cf.mk
@@ -37,6 +37,8 @@
 # All components inherited here go to vendor image
 #
 $(call inherit-product, device/google/cuttlefish/shared/phone/device_vendor.mk)
+# TODO(b/205788876) remove this when openwrt has an image for arm.
+PRODUCT_ENFORCE_MAC80211_HWSIM := false
 
 # Nested virtualization support
 $(call inherit-product, packages/modules/Virtualization/apex/product_packages.mk)
diff --git a/vsoc_x86_noapex/BoardConfig.mk b/vsoc_arm64/phone/aosp_cf_hwasan.mk
similarity index 61%
copy from vsoc_x86_noapex/BoardConfig.mk
copy to vsoc_arm64/phone/aosp_cf_hwasan.mk
index 934b3e9..8e19670 100644
--- a/vsoc_x86_noapex/BoardConfig.mk
+++ b/vsoc_arm64/phone/aosp_cf_hwasan.mk
@@ -1,5 +1,5 @@
 #
-# Copyright 2019 The Android Open-Source Project
+# Copyright (C) 2021 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -14,10 +14,11 @@
 # limitations under the License.
 #
 
-#
-# x86 target for Cuttlefish that doesn't support APEX.
-#
+$(call inherit-product, device/google/cuttlefish/vsoc_arm64/phone/aosp_cf.mk)
 
-include device/google/cuttlefish/vsoc_x86/BoardConfig.mk
+PRODUCT_NAME := aosp_cf_arm64_phone_hwasan
 
-TARGET_FLATTEN_APEX := true
+# Add "hwaddress" as a global sanitizer if it's missing.
+ifeq ($(filter hwaddress,$(SANITIZE_TARGET)),)
+  SANITIZE_TARGET := $(strip $(SANITIZE_TARGET) hwaddress)
+endif
diff --git a/vsoc_arm64_only/BoardConfig.mk b/vsoc_arm64_only/BoardConfig.mk
index b5a1340..eede683 100644
--- a/vsoc_arm64_only/BoardConfig.mk
+++ b/vsoc_arm64_only/BoardConfig.mk
@@ -27,7 +27,7 @@
 TARGET_CPU_VARIANT := cortex-a53
 
 AUDIOSERVER_MULTILIB := first
-BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/5.10/arm64/*.ko)
+BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/$(TARGET_KERNEL_USE)/arm64/*.ko)
 
 HOST_CROSS_OS := linux_bionic
 HOST_CROSS_ARCH := arm64
diff --git a/vsoc_arm64/auto/OWNERS b/vsoc_arm64_only/auto/OWNERS
similarity index 100%
rename from vsoc_arm64/auto/OWNERS
rename to vsoc_arm64_only/auto/OWNERS
diff --git a/vsoc_arm64_only/auto/aosp_cf.mk b/vsoc_arm64_only/auto/aosp_cf.mk
new file mode 100644
index 0000000..33a8624
--- /dev/null
+++ b/vsoc_arm64_only/auto/aosp_cf.mk
@@ -0,0 +1,66 @@
+#
+# 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.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_system.mk)
+
+# FIXME: generic_system.mk sets 'PRODUCT_ENFORCE_RRO_TARGETS := *'
+#        but this breaks phone_car. So undo it here.
+PRODUCT_ENFORCE_RRO_TARGETS := frameworks-res
+
+# FIXME: Disable mainline path checks
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := false
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/base_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+LOCAL_DISABLE_OMX := true
+$(call inherit-product, device/google/cuttlefish/shared/auto/device_vendor.mk)
+
+# TODO(b/205788876) remove this when openwrt has an image for arm.
+PRODUCT_ENFORCE_MAC80211_HWSIM := false
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_arm64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_arm64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+
+PRODUCT_NAME := aosp_cf_arm64_auto
+PRODUCT_DEVICE := vsoc_arm64_only
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish arm64 auto
+
+PRODUCT_VENDOR_PROPERTIES += \
+    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_arm64_only/phone/aosp_cf.mk b/vsoc_arm64_only/phone/aosp_cf.mk
index 5bcfc7b..0da151f 100644
--- a/vsoc_arm64_only/phone/aosp_cf.mk
+++ b/vsoc_arm64_only/phone/aosp_cf.mk
@@ -39,6 +39,9 @@
 LOCAL_DISABLE_OMX := true
 $(call inherit-product, device/google/cuttlefish/shared/phone/device_vendor.mk)
 
+# TODO(b/205788876) remove this when openwrt has an image for arm.
+PRODUCT_ENFORCE_MAC80211_HWSIM := false
+
 # Nested virtualization support
 $(call inherit-product, packages/modules/Virtualization/apex/product_packages.mk)
 
diff --git a/vsoc_x86_noapex/BoardConfig.mk b/vsoc_arm64_only/phone/aosp_cf_hwasan.mk
similarity index 60%
copy from vsoc_x86_noapex/BoardConfig.mk
copy to vsoc_arm64_only/phone/aosp_cf_hwasan.mk
index 934b3e9..c261662 100644
--- a/vsoc_x86_noapex/BoardConfig.mk
+++ b/vsoc_arm64_only/phone/aosp_cf_hwasan.mk
@@ -1,5 +1,5 @@
 #
-# Copyright 2019 The Android Open-Source Project
+# Copyright (C) 2021 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -14,10 +14,11 @@
 # limitations under the License.
 #
 
-#
-# x86 target for Cuttlefish that doesn't support APEX.
-#
+$(call inherit-product, device/google/cuttlefish/vsoc_arm64_only/phone/aosp_cf.mk)
 
-include device/google/cuttlefish/vsoc_x86/BoardConfig.mk
+PRODUCT_NAME := aosp_cf_arm64_only_phone_hwasan
 
-TARGET_FLATTEN_APEX := true
+# Add "hwaddress" as a global sanitizer if it's missing.
+ifeq ($(filter hwaddress,$(SANITIZE_TARGET)),)
+  SANITIZE_TARGET := $(strip $(SANITIZE_TARGET) hwaddress)
+endif
diff --git a/vsoc_arm64_only/slim/aosp_cf.mk b/vsoc_arm64_only/slim/aosp_cf.mk
new file mode 100644
index 0000000..8699275
--- /dev/null
+++ b/vsoc_arm64_only/slim/aosp_cf.mk
@@ -0,0 +1,68 @@
+#
+# 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.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_system.mk)
+
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := relaxed
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/media_system_ext.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/media_product.mk)
+PRODUCT_PACKAGES += FakeSystemApp
+
+#
+# All components inherited here go to vendor image
+#
+LOCAL_DISABLE_OMX := true
+LOCAL_PREFER_VENDOR_APEX := true
+$(call inherit-product, device/google/cuttlefish/shared/slim/device_vendor.mk)
+
+# TODO(b/205788876) remove this when openwrt has an image for arm.
+PRODUCT_ENFORCE_MAC80211_HWSIM := false
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_arm64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_arm64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.aosp_cf_slim.hardware.core_permissions
+else
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+endif
+
+PRODUCT_NAME := aosp_cf_arm64_slim
+PRODUCT_DEVICE := vsoc_arm64_only
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish arm64 slim 64-bit only
+
+PRODUCT_VENDOR_PROPERTIES += \
+    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_arm_only/BoardConfig.mk b/vsoc_arm_only/BoardConfig.mk
index e0cb5ce..54c656c 100644
--- a/vsoc_arm_only/BoardConfig.mk
+++ b/vsoc_arm_only/BoardConfig.mk
@@ -27,7 +27,7 @@
 TARGET_CPU_ABI2 := armeabi
 TARGET_CPU_VARIANT := cortex-a15
 
-BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard device/google/cuttlefish_prebuilts/kernel/5.4-arm/*.ko)
+BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard device/google/cuttlefish_prebuilts/kernel/$(TARGET_KERNEL_USE)-arm/*.ko)
 
 HOST_CROSS_OS := linux_bionic
 HOST_CROSS_ARCH := arm64
diff --git a/vsoc_arm_only/bootloader.mk b/vsoc_arm_only/bootloader.mk
index 93de14e..959cd61 100644
--- a/vsoc_arm_only/bootloader.mk
+++ b/vsoc_arm_only/bootloader.mk
@@ -18,5 +18,3 @@
 # FIXME: Copying the QEMU bootloader for now, but this should be updated..
 BOARD_PREBUILT_BOOTLOADER := \
     device/google/cuttlefish_prebuilts/bootloader/qemu_arm/u-boot.bin
-PRODUCT_COPY_FILES += \
-    device/google/cuttlefish_prebuilts/bootloader/qemu_arm/u-boot.bin:bootloader.qemu
diff --git a/vsoc_arm_only/kernel.mk b/vsoc_arm_only/kernel.mk
index f7472e7..a216444 100644
--- a/vsoc_arm_only/kernel.mk
+++ b/vsoc_arm_only/kernel.mk
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-TARGET_KERNEL_PATH ?= device/google/cuttlefish_prebuilts/kernel/5.4-arm/kernel-5.4
+TARGET_KERNEL_USE ?= mainline
+TARGET_KERNEL_PATH ?= device/google/cuttlefish_prebuilts/kernel/$(TARGET_KERNEL_USE)-arm/kernel-$(TARGET_KERNEL_USE)
 
 PRODUCT_COPY_FILES += $(TARGET_KERNEL_PATH):kernel
diff --git a/vsoc_arm_only/phone/aosp_cf.mk b/vsoc_arm_only/phone/aosp_cf.mk
index 2643c90..751c503 100644
--- a/vsoc_arm_only/phone/aosp_cf.mk
+++ b/vsoc_arm_only/phone/aosp_cf.mk
@@ -44,13 +44,15 @@
     system/app/PlatformCaptivePortalLogin/PlatformCaptivePortalLogin.apk \
     system/priv-app/CellBroadcastServiceModulePlatform/CellBroadcastServiceModulePlatform.apk \
     system/priv-app/InProcessNetworkStack/InProcessNetworkStack.apk \
-    system/priv-app/PlatformNetworkPermissionConfig/PlatformNetworkPermissionConfig.apk \
 
 #
 # All components inherited here go to vendor image
 #
 $(call inherit-product, device/google/cuttlefish/shared/phone/device_vendor.mk)
 
+# TODO(b/205788876) remove this when openwrt has an image for arm.
+PRODUCT_ENFORCE_MAC80211_HWSIM := false
+
 #
 # Special settings for the target
 #
@@ -67,5 +69,10 @@
 PRODUCT_MODEL := Cuttlefish arm phone 32-bit only
 
 PRODUCT_VENDOR_PROPERTIES += \
+    ro.config.low_ram=true \
     ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
     ro.soc.model=$(PRODUCT_DEVICE)
+
+TARGET_SYSTEM_PROP += \
+    build/make/target/board/go_defaults_512.prop \
+    build/make/target/board/go_defaults_common.prop
diff --git a/vsoc_x86/BoardConfig.mk b/vsoc_x86/BoardConfig.mk
index 70db8c2..cd2c9d4 100644
--- a/vsoc_x86/BoardConfig.mk
+++ b/vsoc_x86/BoardConfig.mk
@@ -30,10 +30,8 @@
 TARGET_NATIVE_BRIDGE_CPU_VARIANT := generic
 TARGET_NATIVE_BRIDGE_ABI := armeabi-v7a armeabi
 
-BUILD_BROKEN_DUP_RULES := true
-
 ifeq ($(BOARD_VENDOR_RAMDISK_KERNEL_MODULES),)
-    BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/5.10/x86-64/*.ko)
+    BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/$(TARGET_KERNEL_USE)/x86-64/*.ko)
 endif
 
 # TODO(b/156534160): Temporarily allow for the old style PRODUCT_COPY_FILES for ndk_translation_prebuilt
diff --git a/vsoc_x86/auto/aosp_cf.mk b/vsoc_x86/auto/aosp_cf.mk
new file mode 100644
index 0000000..ecd9063
--- /dev/null
+++ b/vsoc_x86/auto/aosp_cf.mk
@@ -0,0 +1,61 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_system.mk)
+
+# FIXME: generic_system.mk sets 'PRODUCT_ENFORCE_RRO_TARGETS := *'
+#        but this breaks phone_car. So undo it here.
+PRODUCT_ENFORCE_RRO_TARGETS := frameworks-res
+
+# FIXME: Disable mainline path checks
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := false
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/base_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+$(call inherit-product, device/google/cuttlefish/shared/auto/device_vendor.mk)
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+
+PRODUCT_NAME := aosp_cf_x86_auto
+PRODUCT_DEVICE := vsoc_x86
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86 auto
+
+PRODUCT_VENDOR_PROPERTIES += \
+    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/auto/device.mk b/vsoc_x86/auto/device.mk
deleted file mode 100644
index e0d5bad8..0000000
--- a/vsoc_x86/auto/device.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, device/google/cuttlefish/shared/auto/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
-
-PRODUCT_NAME := aosp_cf_x86_auto
-PRODUCT_DEVICE := vsoc_x86
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86 auto
-
-PRODUCT_VENDOR_PROPERTIES += \
-    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
-    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/go/OWNERS b/vsoc_x86/go/OWNERS
new file mode 100644
index 0000000..0c77d0e
--- /dev/null
+++ b/vsoc_x86/go/OWNERS
@@ -0,0 +1,2 @@
+rajekumar@google.com
+tjoines@google.com
diff --git a/vsoc_x86/go/aosp_cf.mk b/vsoc_x86/go/aosp_cf.mk
new file mode 100644
index 0000000..d91c575
--- /dev/null
+++ b/vsoc_x86/go/aosp_cf.mk
@@ -0,0 +1,65 @@
+#
+# 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.
+#
+
+#
+# All components inherited here go to system image (same as GSI system)
+#
+$(call inherit-product, build/target/product/go_defaults.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_system.mk)
+
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := relaxed
+
+# These packages come from go_defaults.mk
+PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST += \
+    system/apex/com.android.tethering.inprocess.capex \
+    system/app/PlatformCaptivePortalLogin/PlatformCaptivePortalLogin.apk \
+    system/priv-app/CellBroadcastServiceModulePlatform/CellBroadcastServiceModulePlatform.apk \
+    system/priv-app/InProcessNetworkStack/InProcessNetworkStack.apk \
+
+#
+# All components inherited here go to system_ext image (same as GSI system_ext)
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_system_ext.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system_ext.mk)
+
+#
+# All components inherited here go to product image (same as GSI product)
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+$(call inherit-product, device/google/cuttlefish/shared/go/device_vendor.mk)
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+
+PRODUCT_NAME := aosp_cf_x86_go_phone
+PRODUCT_DEVICE := vsoc_x86
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86 Go phone
+
+PRODUCT_VENDOR_PROPERTIES += \
+    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/go_512_phone/device.mk b/vsoc_x86/go_512_phone/device.mk
deleted file mode 100644
index 2df9023..0000000
--- a/vsoc_x86/go_512_phone/device.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, device/google/cuttlefish/shared/go_512/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
-
-PRODUCT_NAME := aosp_cf_x86_go_512_phone
-PRODUCT_DEVICE := vsoc_x86
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86 Go 512 phone
-PRODUCT_PACKAGE_OVERLAYS := device/google/cuttlefish/vsoc_x86/phone/overlay
-
-PRODUCT_VENDOR_PROPERTIES += \
-    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
-    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/go_phone/device.mk b/vsoc_x86/go_phone/device.mk
deleted file mode 100644
index 8b2a8f3..0000000
--- a/vsoc_x86/go_phone/device.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, device/google/cuttlefish/shared/go/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
-
-PRODUCT_NAME := aosp_cf_x86_go_phone
-PRODUCT_DEVICE := vsoc_x86
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86 Go phone
-PRODUCT_PACKAGE_OVERLAYS := device/google/cuttlefish/vsoc_x86/phone/overlay
-
-PRODUCT_VENDOR_PROPERTIES += \
-    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
-    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/tv/aosp_cf.mk b/vsoc_x86/tv/aosp_cf.mk
new file mode 100644
index 0000000..757c0a7
--- /dev/null
+++ b/vsoc_x86/tv/aosp_cf.mk
@@ -0,0 +1,56 @@
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, device/google/atv/products/atv_generic_system.mk)
+
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := relaxed
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, device/google/atv/products/atv_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, device/google/atv/products/atv_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+$(call inherit-product, device/google/cuttlefish/shared/tv/device_vendor.mk)
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+
+PRODUCT_NAME := aosp_cf_x86_tv
+PRODUCT_DEVICE := vsoc_x86
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86 tv
+
+PRODUCT_VENDOR_PROPERTIES += \
+    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/tv/device.mk b/vsoc_x86/tv/device.mk
deleted file mode 100644
index b65f50c..0000000
--- a/vsoc_x86/tv/device.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, device/google/cuttlefish/shared/tv/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
-
-PRODUCT_NAME := aosp_cf_x86_tv
-PRODUCT_DEVICE := vsoc_x86
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86 tv
-
-PRODUCT_VENDOR_PROPERTIES += \
-    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
-    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86/wear/aosp_cf.mk b/vsoc_x86/wear/aosp_cf.mk
new file mode 100644
index 0000000..35402f0
--- /dev/null
+++ b/vsoc_x86/wear/aosp_cf.mk
@@ -0,0 +1,77 @@
+#
+# 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.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, device/google/cuttlefish/shared/wear/aosp_system.mk)
+
+# Allowed for wearables, but not installed to /system by default
+PRODUCT_PACKAGES += \
+    cameraserver \
+
+# Cuttlefish uses A/B with system_b preopt, so we must install these
+PRODUCT_PACKAGES += \
+    cppreopts.sh \
+    otapreopt_script \
+
+# Hacks to boot with basic AOSP system apps
+PRODUCT_PACKAGES += \
+    Contacts \
+    Launcher3QuickStep \
+    Provision \
+    Settings \
+    StorageManager \
+    SystemUI \
+
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/android.software.app_widgets.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.app_widgets.xml \
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, device/google/cuttlefish/shared/wear/aosp_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, device/google/cuttlefish/shared/wear/aosp_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+$(call inherit-product, device/google/cuttlefish/shared/wear/aosp_vendor.mk)
+$(call inherit-product, device/google/cuttlefish/shared/wear/device_vendor.mk)
+PRODUCT_ENFORCE_MAC80211_HWSIM := false
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml \
+
+PRODUCT_NAME := aosp_cf_x86_wear
+PRODUCT_DEVICE := vsoc_x86
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86 wearable
+
+PRODUCT_VENDOR_PROPERTIES += \
+    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_64/BoardConfig.mk b/vsoc_x86_64/BoardConfig.mk
index 52dde5c..a740a76 100644
--- a/vsoc_x86_64/BoardConfig.mk
+++ b/vsoc_x86_64/BoardConfig.mk
@@ -28,7 +28,6 @@
 TARGET_2ND_ARCH := x86
 TARGET_2ND_CPU_ABI := x86
 TARGET_2ND_ARCH_VARIANT := silvermont
-TARGET_2ND_CPU_VARIANT := silvermont
 
 TARGET_NATIVE_BRIDGE_ARCH := arm64
 TARGET_NATIVE_BRIDGE_ARCH_VARIANT := armv8-a
@@ -40,8 +39,6 @@
 TARGET_NATIVE_BRIDGE_2ND_CPU_VARIANT := generic
 TARGET_NATIVE_BRIDGE_2ND_ABI := armeabi-v7a armeabi
 
-BUILD_BROKEN_DUP_RULES := true
-
 ifeq ($(BOARD_VENDOR_RAMDISK_KERNEL_MODULES),)
     BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/$(TARGET_KERNEL_USE)/x86-64/*.ko)
 endif
diff --git a/vsoc_x86_64/auto/aosp_cf.mk b/vsoc_x86_64/auto/aosp_cf.mk
new file mode 100644
index 0000000..610c8c1
--- /dev/null
+++ b/vsoc_x86_64/auto/aosp_cf.mk
@@ -0,0 +1,62 @@
+#
+# 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.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_system.mk)
+
+# FIXME: generic_system.mk sets 'PRODUCT_ENFORCE_RRO_TARGETS := *'
+#        but this breaks phone_car. So undo it here.
+PRODUCT_ENFORCE_RRO_TARGETS := frameworks-res
+
+# FIXME: Disable mainline path checks
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := false
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/base_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+$(call inherit-product, device/google/cuttlefish/shared/auto/device_vendor.mk)
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+
+PRODUCT_NAME := aosp_cf_x86_64_auto
+PRODUCT_DEVICE := vsoc_x86_64
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86_64 auto
+
+PRODUCT_VENDOR_PROPERTIES += \
+    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_64/auto/device.mk b/vsoc_x86_64/auto/device.mk
deleted file mode 100644
index b21f694..0000000
--- a/vsoc_x86_64/auto/device.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
-$(call inherit-product, device/google/cuttlefish/shared/auto/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
-
-PRODUCT_NAME := aosp_cf_x86_64_auto
-PRODUCT_DEVICE := vsoc_x86_64
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86_64 auto
-
-PRODUCT_VENDOR_PROPERTIES += \
-    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
-    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_64/bootloader.mk b/vsoc_x86_64/bootloader.mk
index b0d8c5d..6294ca7 100644
--- a/vsoc_x86_64/bootloader.mk
+++ b/vsoc_x86_64/bootloader.mk
@@ -17,5 +17,3 @@
 TARGET_NO_BOOTLOADER := false
 BOARD_PREBUILT_BOOTLOADER := \
     device/google/cuttlefish_prebuilts/bootloader/crosvm_x86_64/u-boot.rom
-PRODUCT_COPY_FILES += \
-    device/google/cuttlefish_prebuilts/bootloader/qemu_x86_64/u-boot.rom:bootloader.qemu
diff --git a/vsoc_x86_64/kernel.mk b/vsoc_x86_64/kernel.mk
index 112eb25..e087f22 100644
--- a/vsoc_x86_64/kernel.mk
+++ b/vsoc_x86_64/kernel.mk
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-TARGET_KERNEL_USE ?= 5.10
+TARGET_KERNEL_USE ?= 5.15
 TARGET_KERNEL_PATH ?= kernel/prebuilts/$(TARGET_KERNEL_USE)/x86_64/kernel-$(TARGET_KERNEL_USE)
 
 PRODUCT_COPY_FILES += $(TARGET_KERNEL_PATH):kernel
diff --git a/vsoc_x86_64/phone/aosp_cf.mk b/vsoc_x86_64/phone/aosp_cf.mk
index 478452f..2be93fd 100644
--- a/vsoc_x86_64/phone/aosp_cf.mk
+++ b/vsoc_x86_64/phone/aosp_cf.mk
@@ -36,6 +36,7 @@
 #
 # All components inherited here go to vendor image
 #
+LOCAL_PREFER_VENDOR_APEX := true
 $(call inherit-product, device/google/cuttlefish/shared/phone/device_vendor.mk)
 
 # Nested virtualization support
@@ -48,14 +49,24 @@
 $(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
 
 # Exclude features that are not available on AOSP devices.
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.aosp_cf_phone.hardware.core_permissions
+else
 PRODUCT_COPY_FILES += \
     frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+endif
 
 PRODUCT_NAME := aosp_cf_x86_64_phone
 PRODUCT_DEVICE := vsoc_x86_64
 PRODUCT_MANUFACTURER := Google
 PRODUCT_MODEL := Cuttlefish x86_64 phone
 
+# Window sidecar and extensions to enhance activity embedding, multi-display,
+# tablet, and foldable support.
+PRODUCT_PACKAGES += \
+    androidx.window.extensions \
+    androidx.window.sidecar \
+
 PRODUCT_VENDOR_PROPERTIES += \
     ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
     ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_64/phone/aosp_cf_foldable.mk b/vsoc_x86_64/phone/aosp_cf_foldable.mk
index c0872d8..b8a0a16 100644
--- a/vsoc_x86_64/phone/aosp_cf_foldable.mk
+++ b/vsoc_x86_64/phone/aosp_cf_foldable.mk
@@ -25,14 +25,7 @@
     device/google/cuttlefish/shared/foldable/display_layout_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/displayconfig/display_layout_configuration.xml \
     device/google/cuttlefish/shared/foldable/display_settings.xml:$(TARGET_COPY_OUT_VENDOR)/etc/display_settings.xml \
 
-# Sidecar and window extensions enhance multi-display, tablet, and foldable support.
-PRODUCT_PACKAGES += \
-    androidx.window.extensions \
-    androidx.window.sidecar \
-
 # Include RRO settings that specify the fold states and screen information.
 DEVICE_PACKAGE_OVERLAYS += device/google/cuttlefish/shared/foldable/overlay
-# Include the foldable `launch_cvd --config foldable` option.
-SOONG_CONFIG_cvd_launch_configs += cvd_config_foldable.json
 # Include the android-info.txt that specifies the foldable --config by default.
 TARGET_BOARD_INFO_FILE := device/google/cuttlefish/shared/foldable/android-info.txt
diff --git a/vsoc_x86_64/tv/OWNERS b/vsoc_x86_64/tv/OWNERS
new file mode 100644
index 0000000..4df9f27
--- /dev/null
+++ b/vsoc_x86_64/tv/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 760438
+include device/google/atv:/OWNERS
diff --git a/vsoc_x86_64/tv/aosp_cf.mk b/vsoc_x86_64/tv/aosp_cf.mk
new file mode 100644
index 0000000..c59f1b0
--- /dev/null
+++ b/vsoc_x86_64/tv/aosp_cf.mk
@@ -0,0 +1,57 @@
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
+$(call inherit-product, device/google/atv/products/atv_generic_system.mk)
+
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := relaxed
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, device/google/atv/products/atv_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, device/google/atv/products/atv_product.mk)
+
+#
+# All components inherited here go to vendor image
+#
+$(call inherit-product, device/google/cuttlefish/shared/tv/device_vendor.mk)
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+
+PRODUCT_NAME := aosp_cf_x86_64_tv
+PRODUCT_DEVICE := vsoc_x86_64
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86_64 tv
+
+PRODUCT_VENDOR_PROPERTIES += \
+    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_64/tv/device.mk b/vsoc_x86_64/tv/device.mk
deleted file mode 100644
index d25210d..0000000
--- a/vsoc_x86_64/tv/device.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Copyright (C) 2021 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
-$(call inherit-product, device/google/cuttlefish/shared/tv/device.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
-$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
-
-PRODUCT_NAME := aosp_cf_x86_64_tv
-PRODUCT_DEVICE := vsoc_x86_64
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86_64 tv
-
-PRODUCT_VENDOR_PROPERTIES += \
-    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
-    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_64_only/BoardConfig.mk b/vsoc_x86_64_only/BoardConfig.mk
index d702533..7185b2d 100644
--- a/vsoc_x86_64_only/BoardConfig.mk
+++ b/vsoc_x86_64_only/BoardConfig.mk
@@ -31,5 +31,4 @@
 TARGET_NATIVE_BRIDGE_ABI := arm64-v8a
 
 AUDIOSERVER_MULTILIB := first
-BUILD_BROKEN_DUP_RULES := true
-BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/5.10/x86-64/*.ko)
+BOARD_VENDOR_RAMDISK_KERNEL_MODULES += $(wildcard kernel/prebuilts/common-modules/virtual-device/$(TARGET_KERNEL_USE)/x86-64/*.ko)
diff --git a/vsoc_x86_64_only/slim/aosp_cf.mk b/vsoc_x86_64_only/slim/aosp_cf.mk
new file mode 100644
index 0000000..c7ca328
--- /dev/null
+++ b/vsoc_x86_64_only/slim/aosp_cf.mk
@@ -0,0 +1,65 @@
+#
+# 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.
+#
+
+#
+# All components inherited here go to system image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_system.mk)
+
+PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := relaxed
+
+#
+# All components inherited here go to system_ext image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/media_system_ext.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system_ext.mk)
+
+#
+# All components inherited here go to product image
+#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/media_product.mk)
+PRODUCT_PACKAGES += FakeSystemApp
+
+#
+# All components inherited here go to vendor image
+#
+LOCAL_DISABLE_OMX := true
+LOCAL_PREFER_VENDOR_APEX := true
+$(call inherit-product, device/google/cuttlefish/shared/slim/device_vendor.mk)
+
+#
+# Special settings for the target
+#
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/kernel.mk)
+$(call inherit-product, device/google/cuttlefish/vsoc_x86_64/bootloader.mk)
+
+# Exclude features that are not available on AOSP devices.
+ifeq ($(LOCAL_PREFER_VENDOR_APEX),true)
+PRODUCT_PACKAGES += com.google.aosp_cf_slim.hardware.core_permissions
+else
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/aosp_excluded_hardware.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/aosp_excluded_hardware.xml
+endif
+
+PRODUCT_NAME := aosp_cf_x86_64_slim
+PRODUCT_DEVICE := vsoc_x86_64_only
+PRODUCT_MANUFACTURER := Google
+PRODUCT_MODEL := Cuttlefish x86_64 slim 64-bit only
+
+PRODUCT_VENDOR_PROPERTIES += \
+    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
+    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_noapex/aosp_cf_noapex.mk b/vsoc_x86_noapex/aosp_cf_noapex.mk
deleted file mode 100644
index aa5d3b2..0000000
--- a/vsoc_x86_noapex/aosp_cf_noapex.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Order of this and the following statements is important.
-# Putting this first in the list takes precedence over the one inherited from
-# aosp_cf.
-OVERRIDE_TARGET_FLATTEN_APEX := true
-
-$(call inherit-product, device/google/cuttlefish/vsoc_x86/phone/aosp_cf.mk)
-
-PRODUCT_NAME := aosp_cf_x86_phone_noapex
-PRODUCT_DEVICE := vsoc_x86_noapex
-PRODUCT_MANUFACTURER := Google
-PRODUCT_MODEL := Cuttlefish x86 phone without APEX support
-
-PRODUCT_VENDOR_PROPERTIES += \
-    ro.soc.manufacturer=$(PRODUCT_MANUFACTURER) \
-    ro.soc.model=$(PRODUCT_DEVICE)
diff --git a/vsoc_x86_only/kernel.mk b/vsoc_x86_only/kernel.mk
index d06c0a1..0eec8d9 100644
--- a/vsoc_x86_only/kernel.mk
+++ b/vsoc_x86_only/kernel.mk
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-TARGET_KERNEL_USE ?= 5.10
+TARGET_KERNEL_USE ?= 5.15
 TARGET_KERNEL_PATH ?= device/google/cuttlefish_prebuilts/kernel/$(TARGET_KERNEL_USE)-i686/kernel-$(TARGET_KERNEL_USE)
 
 PRODUCT_COPY_FILES +=$(TARGET_KERNEL_PATH):kernel