Merge "[ANAPIC Review] The radio config type 1.3 hal is removed, need to sync in cuttlefish" into sc-dev
diff --git a/common/libs/semaphore/semaphore.h b/common/libs/concurrency/semaphore.h
similarity index 64%
rename from common/libs/semaphore/semaphore.h
rename to common/libs/concurrency/semaphore.h
index 49a9fbd..5af43a7 100644
--- a/common/libs/semaphore/semaphore.h
+++ b/common/libs/concurrency/semaphore.h
@@ -22,37 +22,31 @@
 #include <mutex>
 
 namespace cuttlefish {
-/**
- * An ad-hoc semaphore used to track the number of items in all queue
- */
 class Semaphore {
  public:
-  Semaphore(const int init_val = 0) : count_{init_val} {}
+  Semaphore(const unsigned int init_val = 0, const unsigned int cap = 30000)
+      : count_{init_val}, capacity_{cap} {}
 
-  // called by the threads that consumes all of the multiple queues
   void SemWait() {
     std::unique_lock<std::mutex> lock(mtx_);
-    cv_.wait(lock, [this]() -> bool { return this->count_ > 0; });
+    resoure_cv_.wait(lock, [this]() -> bool { return count_ > 0; });
     --count_;
+    room_cv_.notify_one();
   }
 
-  // called by each producer thread effectively, whenever an item is added
   void SemPost() {
     std::unique_lock<std::mutex> lock(mtx_);
-    if (++count_ > 0) {
-      cv_.notify_all();
-    }
+    room_cv_.wait(lock, [this]() -> bool { return count_ <= capacity_; });
+    ++count_;
+    resoure_cv_.notify_one();
   }
 
-  void SemWaitItem() { SemWait(); }
-
-  // Only called by the producers
-  void SemPostItem() { SemPost(); }
-
  private:
   std::mutex mtx_;
-  std::condition_variable cv_;
-  int count_;
+  std::condition_variable resoure_cv_;
+  std::condition_variable room_cv_;
+  unsigned int count_;
+  const unsigned int capacity_;  // inclusive upper limit
 };
 
 }  // namespace cuttlefish
diff --git a/common/libs/concurrency/thread_annotations.h b/common/libs/concurrency/thread_annotations.h
new file mode 100644
index 0000000..cd568b3
--- /dev/null
+++ b/common/libs/concurrency/thread_annotations.h
@@ -0,0 +1,72 @@
+#pragma once
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if defined(__SUPPORT_TS_ANNOTATION__) || defined(__clang__)
+#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
+#else
+#define THREAD_ANNOTATION_ATTRIBUTE__(x)  // no-op
+#endif
+
+#define CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
+
+#define SCOPED_CAPABILITY THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
+
+#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
+
+#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
+
+#define ACQUIRED_BEFORE(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
+
+#define ACQUIRED_AFTER(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
+
+#define REQUIRES(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
+
+#define REQUIRES_SHARED(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))
+
+#define ACQUIRE(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))
+
+#define ACQUIRE_SHARED(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))
+
+#define RELEASE(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))
+
+#define RELEASE_SHARED(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))
+
+#define TRY_ACQUIRE(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))
+
+#define TRY_ACQUIRE_SHARED(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))
+
+#define EXCLUDES(...) THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
+
+#define ASSERT_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))
+
+#define ASSERT_SHARED_CAPABILITY(x) \
+  THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))
+
+#define RETURN_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
+
+#define NO_THREAD_SAFETY_ANALYSIS \
+  THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
diff --git a/common/libs/thread_safe_queue/thread_safe_queue.h b/common/libs/concurrency/thread_safe_queue.h
similarity index 99%
rename from common/libs/thread_safe_queue/thread_safe_queue.h
rename to common/libs/concurrency/thread_safe_queue.h
index ca25b75..9105299 100644
--- a/common/libs/thread_safe_queue/thread_safe_queue.h
+++ b/common/libs/concurrency/thread_safe_queue.h
@@ -16,11 +16,11 @@
  * limitations under the License.
  */
 
-#include <mutex>
 #include <condition_variable>
 #include <deque>
-#include <utility>
 #include <iterator>
+#include <mutex>
+#include <utility>
 
 namespace cuttlefish {
 // Simple queue with Push and Pop capabilities.
diff --git a/common/libs/threads/thread_annotations.h b/common/libs/threads/thread_annotations.h
deleted file mode 100644
index 3ba67e7..0000000
--- a/common/libs/threads/thread_annotations.h
+++ /dev/null
@@ -1,79 +0,0 @@
-#pragma once
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#if defined(__SUPPORT_TS_ANNOTATION__) || defined(__clang__)
-#define THREAD_ANNOTATION_ATTRIBUTE__(x)   __attribute__((x))
-#else
-#define THREAD_ANNOTATION_ATTRIBUTE__(x)   // no-op
-#endif
-
-#define CAPABILITY(x) \
-      THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
-
-#define SCOPED_CAPABILITY \
-      THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
-
-#define GUARDED_BY(x) \
-      THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
-
-#define PT_GUARDED_BY(x) \
-      THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
-
-#define ACQUIRED_BEFORE(...) \
-      THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
-
-#define ACQUIRED_AFTER(...) \
-      THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
-
-#define REQUIRES(...) \
-      THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
-
-#define REQUIRES_SHARED(...) \
-      THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))
-
-#define ACQUIRE(...) \
-      THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))
-
-#define ACQUIRE_SHARED(...) \
-      THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))
-
-#define RELEASE(...) \
-      THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))
-
-#define RELEASE_SHARED(...) \
-      THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))
-
-#define TRY_ACQUIRE(...) \
-      THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))
-
-#define TRY_ACQUIRE_SHARED(...) \
-      THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))
-
-#define EXCLUDES(...) \
-      THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
-
-#define ASSERT_CAPABILITY(x) \
-      THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))
-
-#define ASSERT_SHARED_CAPABILITY(x) \
-      THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))
-
-#define RETURN_CAPABILITY(x) \
-      THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
-
-#define NO_THREAD_SAFETY_ANALYSIS \
-      THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
diff --git a/default-permissions.xml b/default-permissions.xml
index 8f2920e..038ffce 100644
--- a/default-permissions.xml
+++ b/default-permissions.xml
@@ -67,17 +67,6 @@
         <permission name="android.permission.RECEIVE_SMS" fixed="false"/>
     </exception>
 
-    <exception
-            package="com.google.android.projection.gearhead"
-            sha256-cert-digest="FD:B0:0C:43:DB:DE:8B:51:CB:31:2A:A8:1D:3B:5F:A1:77:13:AD:B9:4B:28:F5:98:D7:7F:8E:B8:9D:AC:EE:DF">
-        <!-- For Top Gear -->
-        <permission name="android.permission.PROCESS_OUTGOING_CALLS" fixed="false"/>
-        <permission name="android.permission.READ_SMS" fixed="false"/>
-        <permission name="android.permission.RECEIVE_MMS" fixed="false"/>
-        <permission name="android.permission.WRITE_CALL_LOG" fixed="false"/>
-        <permission name="android.permission.ACCESS_COARSE_LOCATION" fixed="false"/>
-    </exception>
-
     <exception package="com.google.android.settings.intelligence">
         <!-- Calendar -->
         <permission name="android.permission.READ_CALENDAR" fixed="true"/>
diff --git a/guest/commands/rotate/main.cpp b/guest/commands/rotate/main.cpp
deleted file mode 100644
index 8967097..0000000
--- a/guest/commands/rotate/main.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <android-base/chrono_utils.h>
-#include <android-base/logging.h>
-#include <binder/IServiceManager.h>
-#include <utils/StrongPointer.h>
-#include <utils/SystemClock.h>
-
-#include <thread>
-
-#include "android/hardware/sensors/2.0/ISensors.h"
-
-using android::sp;
-using android::hardware::sensors::V1_0::Event;
-using android::hardware::sensors::V1_0::OperationMode;
-using android::hardware::sensors::V1_0::Result;
-using android::hardware::sensors::V1_0::SensorInfo;
-using android::hardware::sensors::V1_0::SensorStatus;
-using android::hardware::sensors::V1_0::SensorType;
-using android::hardware::sensors::V2_0::ISensors;
-
-void InjectOrientation(bool portrait) {
-  const sp<ISensors> sensors = ISensors::getService();
-  if (sensors == nullptr) {
-    LOG(FATAL) << "Unable to get ISensors.";
-  }
-
-  Result result;
-
-  // Place the ISensors HAL into DATA_INJECTION mode so that we can
-  // inject events.
-  result = sensors->setOperationMode(OperationMode::DATA_INJECTION);
-  if (result != Result::OK) {
-    LOG(FATAL) << "Unable to set ISensors operation mode to DATA_INJECTION: "
-               << toString(result);
-  }
-
-  // Find the first available accelerometer sensor.
-  int accel_handle = -1;
-  const auto& getSensorsList_result =
-      sensors->getSensorsList([&](const auto& list) {
-        for (const SensorInfo& sensor : list) {
-          if (sensor.type == SensorType::ACCELEROMETER) {
-            accel_handle = sensor.sensorHandle;
-            break;
-          }
-        }
-      });
-  if (!getSensorsList_result.isOk()) {
-    LOG(FATAL) << "Unable to get ISensors sensors list: "
-               << getSensorsList_result.description();
-  }
-  if (accel_handle == -1) {
-    LOG(FATAL) << "Unable to find ACCELEROMETER sensor.";
-  }
-
-  // Create a base ISensors accelerometer event.
-  Event event;
-  event.sensorHandle = accel_handle;
-  event.sensorType = SensorType::ACCELEROMETER;
-  if (portrait) {
-    event.u.vec3.x = 0;
-    event.u.vec3.y = 9.2;
-  } else {
-    event.u.vec3.x = 9.2;
-    event.u.vec3.y = 0;
-  }
-  event.u.vec3.z = 3.5;
-  event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
-
-  // Repeatedly inject accelerometer events. The WindowManager orientation
-  // listener responds to sustained accelerometer data, not just a single event.
-  android::base::Timer timer;
-  while (timer.duration() < 1s) {
-    event.timestamp = android::elapsedRealtimeNano();
-    result = sensors->injectSensorData(event);
-    if (result != Result::OK) {
-      LOG(FATAL) << "Unable to inject ISensors accelerometer event: "
-                 << toString(result);
-    }
-    std::this_thread::sleep_for(10ms);
-  }
-
-  // Return the ISensors HAL back to NORMAL mode.
-  result = sensors->setOperationMode(OperationMode::NORMAL);
-  if (result != Result::OK) {
-    LOG(FATAL) << "Unable to set sensors operation mode to NORMAL: "
-               << toString(result);
-  }
-}
-
-int main(int argc, char** argv) {
-  if (argc == 1) {
-    LOG(FATAL) << "Expected command line arg 'portrait' or 'landscape'";
-  }
-
-  bool portrait = true;
-  if (!strcmp(argv[1], "portrait")) {
-    portrait = true;
-  } else if (!strcmp(argv[1], "landscape")) {
-    portrait = false;
-  } else {
-    LOG(FATAL) << "Expected command line arg 'portrait' or 'landscape'";
-  }
-
-  InjectOrientation(portrait);
-}
diff --git a/guest/commands/rotate/Android.bp b/guest/commands/sensor_injection/Android.bp
similarity index 79%
rename from guest/commands/rotate/Android.bp
rename to guest/commands/sensor_injection/Android.bp
index 6568c0e..a29250a 100644
--- a/guest/commands/rotate/Android.bp
+++ b/guest/commands/sensor_injection/Android.bp
@@ -3,11 +3,11 @@
 }
 
 cc_binary {
-    name: "cuttlefish_rotate",
+    name: "cuttlefish_sensor_injection",
     srcs: ["main.cpp"],
     shared_libs: [
         "[email protected]",
-        "[email protected]",
+        "[email protected]",
         "libbase",
         "libbinder",
         "libhidlbase",
diff --git a/guest/commands/sensor_injection/main.cpp b/guest/commands/sensor_injection/main.cpp
new file mode 100644
index 0000000..6eadb4a
--- /dev/null
+++ b/guest/commands/sensor_injection/main.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/chrono_utils.h>
+#include <android-base/logging.h>
+#include <binder/IServiceManager.h>
+#include <utils/StrongPointer.h>
+#include <utils/SystemClock.h>
+
+#include <thread>
+
+#include "android/hardware/sensors/2.1/ISensors.h"
+
+using android::sp;
+using android::hardware::sensors::V1_0::OperationMode;
+using android::hardware::sensors::V1_0::Result;
+using android::hardware::sensors::V1_0::SensorStatus;
+using android::hardware::sensors::V2_1::Event;
+using android::hardware::sensors::V2_1::ISensors;
+using android::hardware::sensors::V2_1::SensorInfo;
+using android::hardware::sensors::V2_1::SensorType;
+
+sp<ISensors> startSensorInjection() {
+  const sp<ISensors> sensors = ISensors::getService();
+  if (sensors == nullptr) {
+    LOG(FATAL) << "Unable to get ISensors.";
+  }
+
+  // Place the ISensors HAL into DATA_INJECTION mode so that we can
+  // inject events.
+  Result result = sensors->setOperationMode(OperationMode::DATA_INJECTION);
+  if (result != Result::OK) {
+    LOG(FATAL) << "Unable to set ISensors operation mode to DATA_INJECTION: "
+               << toString(result);
+  }
+
+  return sensors;
+}
+
+int getSensorHandle(SensorType type, const sp<ISensors> sensors) {
+  // Find the first available sensor of the given type.
+  int handle = -1;
+  const auto& getSensorsList_result =
+      sensors->getSensorsList_2_1([&](const auto& list) {
+        for (const SensorInfo& sensor : list) {
+          if (sensor.type == type) {
+            handle = sensor.sensorHandle;
+            break;
+          }
+        }
+      });
+  if (!getSensorsList_result.isOk()) {
+    LOG(FATAL) << "Unable to get ISensors sensors list: "
+               << getSensorsList_result.description();
+  }
+  if (handle == -1) {
+    LOG(FATAL) << "Unable to find sensor.";
+  }
+  return handle;
+}
+
+void endSensorInjection(const sp<ISensors> sensors) {
+  // Return the ISensors HAL back to NORMAL mode.
+  Result result = sensors->setOperationMode(OperationMode::NORMAL);
+  if (result != Result::OK) {
+    LOG(FATAL) << "Unable to set sensors operation mode to NORMAL: "
+               << toString(result);
+  }
+}
+
+// Inject ACCELEROMETER events to corresponding to a given physical
+// device orientation: portrait or landscape.
+void InjectOrientation(bool portrait) {
+  sp<ISensors> sensors = startSensorInjection();
+  int handle = getSensorHandle(SensorType::ACCELEROMETER, sensors);
+
+  // Create a base ISensors accelerometer event.
+  Event event;
+  event.sensorHandle = handle;
+  event.sensorType = SensorType::ACCELEROMETER;
+  if (portrait) {
+    event.u.vec3.x = 0;
+    event.u.vec3.y = 9.2;
+  } else {
+    event.u.vec3.x = 9.2;
+    event.u.vec3.y = 0;
+  }
+  event.u.vec3.z = 3.5;
+  event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
+
+  // Repeatedly inject accelerometer events. The WindowManager orientation
+  // listener responds to sustained accelerometer data, not just a single event.
+  android::base::Timer timer;
+  Result result;
+  while (timer.duration() < 1s) {
+    event.timestamp = android::elapsedRealtimeNano();
+    result = sensors->injectSensorData_2_1(event);
+    if (result != Result::OK) {
+      LOG(FATAL) << "Unable to inject ISensors accelerometer event: "
+                 << toString(result);
+    }
+    std::this_thread::sleep_for(10ms);
+  }
+
+  endSensorInjection(sensors);
+}
+
+// Inject a single HINGE_ANGLE event at the given angle.
+void InjectHingeAngle(int angle) {
+  sp<ISensors> sensors = startSensorInjection();
+  int handle = getSensorHandle(SensorType::HINGE_ANGLE, sensors);
+
+  // Create a base ISensors hinge_angle event.
+  Event event;
+  event.sensorHandle = handle;
+  event.sensorType = SensorType::HINGE_ANGLE;
+  event.u.scalar = angle;
+  event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
+  event.timestamp = android::elapsedRealtimeNano();
+  Result result = sensors->injectSensorData_2_1(event);
+  if (result != Result::OK) {
+    LOG(FATAL) << "Unable to inject HINGE_ANGLE data: " << toString(result);
+  }
+
+  endSensorInjection(sensors);
+}
+
+int main(int argc, char** argv) {
+  if (argc == 2) {
+    LOG(FATAL) << "Expected command line args 'rotate <portrait|landscape>' or "
+                  "'hinge_angle <value>'";
+  }
+
+  if (!strcmp(argv[1], "rotate")) {
+    bool portrait = true;
+    if (!strcmp(argv[2], "portrait")) {
+      portrait = true;
+    } else if (!strcmp(argv[2], "landscape")) {
+      portrait = false;
+    } else {
+      LOG(FATAL) << "Expected command line arg 'portrait' or 'landscape'";
+    }
+    InjectOrientation(portrait);
+  } else if (!strcmp(argv[1], "hinge_angle")) {
+    int angle = std::stoi(argv[2]);
+    if (angle < 0 || angle > 360) {
+      LOG(FATAL) << "Bad hinge_angle value: " << argv[2];
+    }
+    InjectHingeAngle(angle);
+  } else {
+    LOG(FATAL) << "Unknown arg: " << argv[1];
+  }
+}
diff --git a/guest/hals/audio/audio_hw.c b/guest/hals/audio/audio_hw.c
index f5048df..652d747 100644
--- a/guest/hals/audio/audio_hw.c
+++ b/guest/hals/audio/audio_hw.c
@@ -1532,7 +1532,7 @@
     }
 
     if (*mic_count == 0) {
-        *mic_count = 1;
+        *mic_count = 0;
         return 0;
     }
 
@@ -1561,7 +1561,7 @@
     mic_array->orientation.y = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
     mic_array->orientation.z = AUDIO_MICROPHONE_COORDINATE_UNKNOWN;
 
-    *mic_count = 1;
+    *mic_count = 0;
     return 0;
 }
 
diff --git a/guest/hals/ril/reference-ril/reference-ril.c b/guest/hals/ril/reference-ril/reference-ril.c
index 9cfdaef..ac77d94 100644
--- a/guest/hals/ril/reference-ril/reference-ril.c
+++ b/guest/hals/ril/reference-ril/reference-ril.c
@@ -783,8 +783,7 @@
 static bool hasWifiCapability()
 {
     char propValue[PROP_VALUE_MAX];
-    return property_get("ro.kernel.qemu.wifi", propValue, "") > 0 &&
-           strcmp("1", propValue) == 0;
+    return property_get("ro.boot.qemu.wifi", propValue, "") > 0 && strcmp("1", propValue) == 0;
 }
 
 static const char* getRadioInterfaceName(bool hasWifi)
diff --git a/host/commands/assemble_cvd/Android.bp b/host/commands/assemble_cvd/Android.bp
index c269be9..4ed4452 100644
--- a/host/commands/assemble_cvd/Android.bp
+++ b/host/commands/assemble_cvd/Android.bp
@@ -17,21 +17,6 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-cc_library {
-    name: "libcdisk_spec",
-    srcs: [
-        "cdisk_spec.proto",
-    ],
-    proto: {
-        type: "full",
-        export_proto_headers: true,
-        include_dirs: [
-            "external/protobuf/src",
-        ],
-    },
-    defaults: ["cuttlefish_host"],
-}
-
 cc_binary {
     name: "assemble_cvd",
     srcs: [
@@ -42,7 +27,6 @@
         "clean.cc",
         "disk_flags.cc",
         "flags.cc",
-        "image_aggregator.cc",
         "misc_info.cc",
         "super_image_mixer.cc",
     ],
@@ -63,6 +47,7 @@
     static_libs: [
         "libcdisk_spec",
         "libext2_uuid",
+        "libimage_aggregator",
         "libsparse",
         "libcuttlefish_graphics_detector",
         "libcuttlefish_host_config",
diff --git a/host/commands/assemble_cvd/assemble_cvd.cc b/host/commands/assemble_cvd/assemble_cvd.cc
index 1350bb6..c249473 100644
--- a/host/commands/assemble_cvd/assemble_cvd.cc
+++ b/host/commands/assemble_cvd/assemble_cvd.cc
@@ -144,7 +144,6 @@
       preserving.insert("os_composite_gpt_footer.img");
       preserving.insert("os_composite.img");
       preserving.insert("sdcard.img");
-      preserving.insert("uboot_env.img");
       preserving.insert("boot_repacked.img");
       preserving.insert("vendor_boot_repacked.img");
       preserving.insert("access-kregistry");
@@ -153,6 +152,11 @@
       preserving.insert("gatekeeper_insecure");
       preserving.insert("modem_nvram.json");
       preserving.insert("recording");
+      preserving.insert("persistent_composite_disk_config.txt");
+      preserving.insert("persistent_composite_gpt_header.img");
+      preserving.insert("persistent_composite_gpt_footer.img");
+      preserving.insert("persistent_composite.img");
+      preserving.insert("uboot_env.img");
       preserving.insert("factory_reset_protected.img");
       std::stringstream ss;
       for (int i = 0; i < FLAGS_modem_simulator_count; i++) {
diff --git a/host/commands/assemble_cvd/disk_flags.cc b/host/commands/assemble_cvd/disk_flags.cc
index 21a5cca..96a4799 100644
--- a/host/commands/assemble_cvd/disk_flags.cc
+++ b/host/commands/assemble_cvd/disk_flags.cc
@@ -28,11 +28,11 @@
 #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/image_aggregator.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"
 
 // Taken from external/avb/libavb/avb_slot_verify.c; this define is not in the headers
@@ -121,14 +121,6 @@
 std::vector<ImagePartition> os_composite_disk_config(
     const CuttlefishConfig::InstanceSpecific& instance) {
   std::vector<ImagePartition> partitions;
-
-  // Note that if the positions of env or misc change, the environment for
-  // u-boot must be updated as well (see boot_config.cc and
-  // configs/cf-x86_defconfig in external/u-boot).
-  partitions.push_back(ImagePartition {
-    .label = "uboot_env",
-    .image_file_path = instance.uboot_env_image_path(),
-  });
   partitions.push_back(ImagePartition {
     .label = "misc",
     .image_file_path = FLAGS_misc_image,
@@ -200,10 +192,34 @@
   return partitions;
 }
 
+std::vector<ImagePartition> persistent_composite_disk_config(
+    const CuttlefishConfig::InstanceSpecific& instance) {
+  std::vector<ImagePartition> partitions;
+
+  // Note that if the position of uboot_env changes, the environment for
+  // u-boot must be updated as well (see boot_config.cc and
+  // cuttlefish.fragment in external/u-boot).
+  partitions.push_back(ImagePartition{
+      .label = "uboot_env",
+      .image_file_path = instance.uboot_env_image_path(),
+  });
+  if (!FLAGS_protected_vm) {
+    partitions.push_back(ImagePartition{
+        .label = "frp",
+        .image_file_path = instance.factory_reset_protected_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;
@@ -216,10 +232,6 @@
   std::chrono::system_clock::time_point youngest_disk_img;
   for (auto& partition :
        os_composite_disk_config(config.ForDefaultInstance())) {
-    if (partition.label == "uboot_env") {
-      continue;
-    }
-
     auto partition_mod_time = FileModificationTime(partition.image_file_path);
     if (partition_mod_time > youngest_disk_img) {
       youngest_disk_img = partition_mod_time;
@@ -338,6 +350,30 @@
   return true;
 }
 
+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;
+}
+
 static void RepackAllBootImages(const CuttlefishConfig* config) {
   CHECK(FileHasContent(FLAGS_boot_image))
       << "File not found: " << FLAGS_boot_image;
@@ -414,13 +450,6 @@
   if (!FLAGS_protected_vm) {
     RepackAllBootImages(config);
 
-    // TODO(b/181812679) remove this block when we no longer need to create the
-    // env partition.
-    for (auto instance : config->Instances()) {
-      CHECK(InitBootloaderEnvPartition(*config, instance))
-          << "Failed to create bootloader environment partition";
-    }
-
     for (const auto& instance : config->Instances()) {
       if (!FileExists(instance.access_kregistry_path())) {
         CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none");
@@ -435,6 +464,9 @@
                          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");
@@ -442,6 +474,20 @@
     }
   }
 
+  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 }) {
diff --git a/host/commands/assemble_cvd/disk_flags.h b/host/commands/assemble_cvd/disk_flags.h
index 122a728..3491c3a 100644
--- a/host/commands/assemble_cvd/disk_flags.h
+++ b/host/commands/assemble_cvd/disk_flags.h
@@ -20,7 +20,6 @@
 #include <memory>
 #include <vector>
 
-#include "host/commands/assemble_cvd/image_aggregator.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/fetcher_config.h"
 
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index bfb856b..2d3f9da 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -708,8 +708,8 @@
           {const_instance.PerInstancePath("os_composite.img")});
     } else {
       std::vector<std::string> virtual_disk_paths = {
-        const_instance.PerInstancePath("overlay.img"),
-        const_instance.factory_reset_protected_path(),
+          const_instance.PerInstancePath("overlay.img"),
+          const_instance.PerInstancePath("persistent_composite.img"),
       };
       if (FLAGS_use_sdcard) {
         virtual_disk_paths.push_back(const_instance.sdcard_path());
diff --git a/host/commands/console_forwarder/main.cpp b/host/commands/console_forwarder/main.cpp
index d12d66b..df5c9f7 100644
--- a/host/commands/console_forwarder/main.cpp
+++ b/host/commands/console_forwarder/main.cpp
@@ -38,6 +38,8 @@
              -1,
              "File descriptor for the console's output channel");
 
+namespace cuttlefish {
+
 // Handles forwarding the serial console to a pseudo-terminal (PTY)
 // It receives a couple of fds for the console (could be the same fd twice if,
 // for example a socket_pair were used).
@@ -49,14 +51,12 @@
 // protected by a mutex.
 class ConsoleForwarder {
  public:
-  ConsoleForwarder(std::string console_path,
-                   cuttlefish::SharedFD console_in,
-                   cuttlefish::SharedFD console_out,
-                   cuttlefish::SharedFD console_log) :
-                                                console_path_(console_path),
-                                                console_in_(console_in),
-                                                console_out_(console_out),
-                                                console_log_(console_log) {}
+  ConsoleForwarder(std::string console_path, SharedFD console_in,
+                   SharedFD console_out, SharedFD console_log)
+      : console_path_(console_path),
+        console_in_(console_in),
+        console_out_(console_out),
+        console_log_(console_log) {}
   [[noreturn]] void StartServer() {
     // Create a new thread to handle writes to the console
     writer_thread_ = std::thread([this]() { WriteLoop(); });
@@ -65,61 +65,45 @@
     ReadLoop();
   }
  private:
-
-  cuttlefish::SharedFD OpenPTY() {
+  SharedFD OpenPTY() {
     // Remove any stale symlink to a pts device
     auto ret = unlink(console_path_.c_str());
-    if (ret < 0 && errno != ENOENT) {
-      LOG(ERROR) << "Failed to unlink " << console_path_.c_str()
-                 << ": " << strerror(errno);
-      std::exit(-5);
-    }
+    CHECK(!(ret < 0 && errno != ENOENT))
+        << "Failed to unlink " << console_path_ << ": " << strerror(errno);
 
     auto pty = posix_openpt(O_RDWR | O_NOCTTY | O_NONBLOCK);
-    if (pty < 0) {
-      LOG(ERROR) << "Failed to open a PTY: " << strerror(errno);
-      std::exit(-6);
-    }
+    CHECK(pty >= 0) << "Failed to open a PTY: " << strerror(errno);
+
     grantpt(pty);
     unlockpt(pty);
 
     // Disable all echo modes on the PTY
     struct termios termios;
-    if (tcgetattr(pty, &termios) < 0) {
-      LOG(ERROR) << "Failed to get terminal control: " << strerror(errno);
-      std::exit(-7);
-    }
+    CHECK(tcgetattr(pty, &termios) >= 0)
+        << "Failed to get terminal control: " << strerror(errno);
+
     termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
     termios.c_oflag &= ~(ONLCR);
-    if (tcsetattr(pty, TCSANOW, &termios) < 0) {
-      LOG(ERROR) << "Failed to set terminal control: " << strerror(errno);
-      std::exit(-8);
-    }
+    CHECK(tcsetattr(pty, TCSANOW, &termios) >= 0)
+        << "Failed to set terminal control: " << strerror(errno);
 
     auto pty_dev_name = ptsname(pty);
-    if (pty_dev_name == nullptr) {
-      LOG(ERROR) << "Failed to obtain PTY device name: " << strerror(errno);
-      std::exit(-9);
-    }
+    CHECK(pty_dev_name != nullptr)
+        << "Failed to obtain PTY device name: " << strerror(errno);
 
-    if (symlink(pty_dev_name, console_path_.c_str()) < 0) {
-      LOG(ERROR) << "Failed to create symlink to " << pty_dev_name << " at "
-                 << console_path_.c_str() << ": " << strerror(errno);
-      std::exit(-10);
-    }
+    CHECK(symlink(pty_dev_name, console_path_.c_str()) >= 0)
+        << "Failed to create symlink to " << pty_dev_name << " at "
+        << console_path_ << ": " << strerror(errno);
 
-    auto pty_shared_fd = cuttlefish::SharedFD::Dup(pty);
+    auto pty_shared_fd = SharedFD::Dup(pty);
     close(pty);
-    if (!pty_shared_fd->IsOpen()) {
-      LOG(ERROR) << "Error dupping fd " << pty << ": "
-                 << pty_shared_fd->StrError();
-      std::exit(-11);
-    }
+    CHECK(pty_shared_fd->IsOpen())
+        << "Error dupping fd " << pty << ": " << pty_shared_fd->StrError();
 
     return pty_shared_fd;
   }
 
-  void EnqueueWrite(std::shared_ptr<std::vector<char>> buf_ptr, cuttlefish::SharedFD fd) {
+  void EnqueueWrite(std::shared_ptr<std::vector<char>> buf_ptr, SharedFD fd) {
     std::lock_guard<std::mutex> lock(write_queue_mutex_);
     write_queue_.emplace_back(fd, buf_ptr);
     condvar_.notify_one();
@@ -129,7 +113,7 @@
     while (true) {
       while (!write_queue_.empty()) {
         std::shared_ptr<std::vector<char>> buf_ptr;
-        cuttlefish::SharedFD fd;
+        SharedFD fd;
         {
           std::lock_guard<std::mutex> lock(write_queue_mutex_);
           auto& front = write_queue_.front();
@@ -170,26 +154,23 @@
   }
 
   [[noreturn]] void ReadLoop() {
-    cuttlefish::SharedFD client_fd;
+    SharedFD client_fd;
     while (true) {
       if (!client_fd->IsOpen()) {
         client_fd = OpenPTY();
       }
 
-      cuttlefish::SharedFDSet read_set;
+      SharedFDSet read_set;
       read_set.Set(console_out_);
       read_set.Set(client_fd);
 
-      cuttlefish::Select(&read_set, nullptr, nullptr, nullptr);
+      Select(&read_set, nullptr, nullptr, nullptr);
       if (read_set.IsSet(console_out_)) {
         std::shared_ptr<std::vector<char>> buf_ptr = std::make_shared<std::vector<char>>(4096);
         auto bytes_read = console_out_->Read(buf_ptr->data(), buf_ptr->size());
-        if (bytes_read <= 0) {
-          LOG(ERROR) << "Error reading from console output: "
-                     << console_out_->StrError();
-          // This is likely unrecoverable, so exit here
-          std::exit(-12);
-        }
+        // This is likely unrecoverable, so exit here
+        CHECK(bytes_read > 0) << "Error reading from console output: "
+                              << console_out_->StrError();
         buf_ptr->resize(bytes_read);
         EnqueueWrite(buf_ptr, console_log_);
         if (client_fd->IsOpen()) {
@@ -215,59 +196,53 @@
   }
 
   std::string console_path_;
-  cuttlefish::SharedFD console_in_;
-  cuttlefish::SharedFD console_out_;
-  cuttlefish::SharedFD console_log_;
+  SharedFD console_in_;
+  SharedFD console_out_;
+  SharedFD console_log_;
   std::thread writer_thread_;
   std::mutex write_queue_mutex_;
   std::condition_variable condvar_;
-  std::deque<std::pair<cuttlefish::SharedFD, std::shared_ptr<std::vector<char>>>> write_queue_;
+  std::deque<std::pair<SharedFD, std::shared_ptr<std::vector<char>>>>
+      write_queue_;
 };
 
-int main(int argc, char** argv) {
-  cuttlefish::DefaultSubprocessLogging(argv);
+int ConsoleForwarderMain(int argc, char** argv) {
+  DefaultSubprocessLogging(argv);
   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
 
-  if (FLAGS_console_in_fd < 0 || FLAGS_console_out_fd < 0) {
-    LOG(ERROR) << "Invalid file descriptors: " << FLAGS_console_in_fd << ", "
-               << FLAGS_console_out_fd;
-    return -1;
-  }
+  CHECK(!(FLAGS_console_in_fd < 0 || FLAGS_console_out_fd < 0))
+      << "Invalid file descriptors: " << FLAGS_console_in_fd << ", "
+      << FLAGS_console_out_fd;
 
-  auto console_in = cuttlefish::SharedFD::Dup(FLAGS_console_in_fd);
-  close(FLAGS_console_in_fd);
-  if (!console_in->IsOpen()) {
-    LOG(ERROR) << "Error dupping fd " << FLAGS_console_in_fd << ": "
-               << console_in->StrError();
-    return -2;
-  }
+  auto console_in = SharedFD::Dup(FLAGS_console_in_fd);
+  CHECK(console_in->IsOpen()) << "Error dupping fd " << FLAGS_console_in_fd
+                              << ": " << console_in->StrError();
   close(FLAGS_console_in_fd);
 
-  auto console_out = cuttlefish::SharedFD::Dup(FLAGS_console_out_fd);
+  auto console_out = SharedFD::Dup(FLAGS_console_out_fd);
+  CHECK(console_out->IsOpen()) << "Error dupping fd " << FLAGS_console_out_fd
+                               << ": " << console_out->StrError();
   close(FLAGS_console_out_fd);
-  if (!console_out->IsOpen()) {
-    LOG(ERROR) << "Error dupping fd " << FLAGS_console_out_fd << ": "
-               << console_out->StrError();
-    return -3;
-  }
 
-  auto config = cuttlefish::CuttlefishConfig::Get();
-  if (!config) {
-    LOG(ERROR) << "Unable to get config object";
-    return -4;
-  }
+  auto config = CuttlefishConfig::Get();
+  CHECK(config) << "Unable to get config object";
 
   auto instance = config->ForDefaultInstance();
   auto console_path = instance.console_path();
   auto console_log = instance.PerInstancePath("console_log");
-  auto console_log_fd = cuttlefish::SharedFD::Open(console_log.c_str(), O_CREAT | O_APPEND | O_WRONLY, 0666);
+  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);
 
   // Don't get a SIGPIPE from the clients
-  if (sigaction(SIGPIPE, nullptr, nullptr) != 0) {
-    LOG(FATAL) << "Failed to set SIGPIPE to be ignored: " << strerror(errno);
-    return -13;
-  }
+  CHECK(sigaction(SIGPIPE, nullptr, nullptr) == 0)
+      << "Failed to set SIGPIPE to be ignored: " << strerror(errno);
 
   console_forwarder.StartServer();
 }
+
+}  // namespace cuttlefish
+
+int main(int argc, char** argv) {
+  return cuttlefish::ConsoleForwarderMain(argc, argv);
+}
diff --git a/host/commands/fetcher/build_api.cc b/host/commands/fetcher/build_api.cc
index 8f97517..69229f0 100644
--- a/host/commands/fetcher/build_api.cc
+++ b/host/commands/fetcher/build_api.cc
@@ -164,8 +164,21 @@
 bool BuildApi::ArtifactToFile(const DeviceBuild& build,
                               const std::string& artifact,
                               const std::string& path) {
-  std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target
-      + "/attempts/latest/artifacts/" + artifact + "?alt=media";
+  std::string url;
+  if (credential_source) {
+    url = BUILD_API + "/builds/" + build.id + "/" + build.target +
+          "/attempts/latest/artifacts/" + artifact + "?alt=media";
+  } else {
+    std::string download_url_endpoint =
+        BUILD_API + "/builds/" + build.id + "/" + build.target +
+        "/attempts/latest/artifacts/" + artifact + "/url";
+    auto download_url_json = curl.DownloadToJson(download_url_endpoint);
+    if (!download_url_json.isMember("signedUrl")) {
+      LOG(ERROR) << "URL endpoint did not have json path";
+      return false;
+    }
+    url = download_url_json["signedUrl"].asString();
+  }
   return curl.DownloadToFile(url, path, Headers());
 }
 
diff --git a/host/commands/mk_cdisk/Android.bp b/host/commands/mk_cdisk/Android.bp
new file mode 100644
index 0000000..266dd15
--- /dev/null
+++ b/host/commands/mk_cdisk/Android.bp
@@ -0,0 +1,40 @@
+//
+// 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: "mk_cdisk",
+    srcs: [
+        "mk_cdisk.cc",
+    ],
+    shared_libs: [
+        "libcuttlefish_fs",
+        "libcuttlefish_utils",
+        "libbase",
+        "libjsoncpp",
+        "libprotobuf-cpp-full",
+        "libz",
+    ],
+    static_libs: [
+        "libcdisk_spec",
+        "libext2_uuid",
+        "libimage_aggregator",
+        "libsparse",
+    ],
+    defaults: ["cuttlefish_host"],
+}
diff --git a/host/commands/mk_cdisk/mk_cdisk.cc b/host/commands/mk_cdisk/mk_cdisk.cc
new file mode 100644
index 0000000..a9949dc
--- /dev/null
+++ b/host/commands/mk_cdisk/mk_cdisk.cc
@@ -0,0 +1,146 @@
+//
+// 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,
+//       (opt) "read_only": bool
+//     }
+//   ]
+// }
+
+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 read_only =
+        part["read_only"].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});
+  }
+
+  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/modem_simulator/misc_service.cpp b/host/commands/modem_simulator/misc_service.cpp
index 0001c4e..1a2b767 100644
--- a/host/commands/modem_simulator/misc_service.cpp
+++ b/host/commands/modem_simulator/misc_service.cpp
@@ -16,6 +16,7 @@
 #include "host/commands/modem_simulator/misc_service.h"
 
 #include <ctime>
+#include <fstream>
 #include <iomanip>
 
 namespace cuttlefish {
@@ -23,7 +24,36 @@
 MiscService::MiscService(int32_t service_id, ChannelMonitor* channel_monitor,
                          ThreadLooper* thread_looper)
     : ModemService(service_id, this->InitializeCommandHandlers(),
-                   channel_monitor, thread_looper) {}
+                   channel_monitor, thread_looper) {
+  ParseTimeZone();
+}
+
+void MiscService::ParseTimeZone() {
+#if defined(__linux__)
+  constexpr char TIMEZONE_FILENAME[] = "/etc/timezone";
+  std::ifstream ifs(TIMEZONE_FILENAME);
+  if (ifs.is_open()) {
+    std::string line;
+    if (std::getline(ifs, line)) {
+      FixTimeZone(line);
+      timezone_ = line;
+    }
+  }
+#endif
+}
+
+void MiscService::FixTimeZone(std::string& line) {
+  auto slashpos = line.find("/");
+  // "/" will be treated as separator, change it !
+  if (slashpos != std::string::npos) {
+    line.replace(slashpos, 1, "!");
+  }
+}
+
+void MiscService::SetTimeZone(std::string timezone) {
+  FixTimeZone(timezone);
+  timezone_ = timezone;
+}
 
 std::vector<CommandHandler> MiscService::InitializeCommandHandlers() {
   std::vector<CommandHandler> command_handlers = {
@@ -118,15 +148,18 @@
   auto tzdiff = (int)std::difftime(t_local_time, t_gm_time) / (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 ? '+' : '-')
-                    << (tzdiff >= 0 ? tzdiff : -tzdiff) << ":"
-                    << local_time.tm_isdst << "\"";
+  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 ? '+' : '-')
+     << (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 e4944ed..e795a06 100644
--- a/host/commands/modem_simulator/misc_service.h
+++ b/host/commands/modem_simulator/misc_service.h
@@ -32,7 +32,12 @@
 
   void TimeUpdate();
 
+  void SetTimeZone(std::string timezone);
+
  private:
+  void ParseTimeZone();
+  void FixTimeZone(std::string& line);
+  std::string timezone_;
   std::vector<CommandHandler> InitializeCommandHandlers();
 };
 
diff --git a/host/commands/modem_simulator/modem_simulator.cpp b/host/commands/modem_simulator/modem_simulator.cpp
index 9c63dc2..61a23bd 100644
--- a/host/commands/modem_simulator/modem_simulator.cpp
+++ b/host/commands/modem_simulator/modem_simulator.cpp
@@ -137,6 +137,13 @@
   }
 }
 
+bool ModemSimulator::IsRadioOn() const {
+  if (network_service_) {
+    return !network_service_->isRadioOff();
+  }
+  return false;
+}
+
 bool ModemSimulator::IsWaitingSmsPdu() {
   if (sms_service_) {
     return (sms_service_->IsWaitingSmsPdu() |
@@ -145,4 +152,10 @@
   return false;
 }
 
+void ModemSimulator::SetTimeZone(std::string timezone) {
+  if (misc_service_) {
+    misc_service_->SetTimeZone(timezone);
+  }
+}
+
 }  // namespace cuttlefish
diff --git a/host/commands/modem_simulator/modem_simulator.h b/host/commands/modem_simulator/modem_simulator.h
index da3be6d..9340e49 100644
--- a/host/commands/modem_simulator/modem_simulator.h
+++ b/host/commands/modem_simulator/modem_simulator.h
@@ -41,10 +41,13 @@
   void OnFirstClientConnected();
   void SaveModemState();
   bool IsWaitingSmsPdu();
+  bool IsRadioOn() const;
   void SetRemoteClient(cuttlefish::SharedFD client, bool is_accepted) {
     channel_monitor_->SetRemoteClient(client, is_accepted);
   }
 
+  void SetTimeZone(std::string timezone);
+
  private:
   int32_t modem_id_;
   std::unique_ptr<ChannelMonitor> channel_monitor_;
diff --git a/host/frontend/vnc_server/Android.bp b/host/frontend/vnc_server/Android.bp
index c098e34..bfb5561 100644
--- a/host/frontend/vnc_server/Android.bp
+++ b/host/frontend/vnc_server/Android.bp
@@ -36,10 +36,18 @@
         "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",
diff --git a/host/frontend/vnc_server/blackboard.h b/host/frontend/vnc_server/blackboard.h
index 261774e..af4baea 100644
--- a/host/frontend/vnc_server/blackboard.h
+++ b/host/frontend/vnc_server/blackboard.h
@@ -22,7 +22,7 @@
 #include <mutex>
 #include <unordered_map>
 
-#include "common/libs/threads/thread_annotations.h"
+#include "common/libs/concurrency/thread_annotations.h"
 #include "host/frontend/vnc_server/vnc_utils.h"
 
 namespace cuttlefish {
diff --git a/host/frontend/vnc_server/frame_buffer_watcher.cpp b/host/frontend/vnc_server/frame_buffer_watcher.cpp
index 697a6d9..3cc39c0 100644
--- a/host/frontend/vnc_server/frame_buffer_watcher.cpp
+++ b/host/frontend/vnc_server/frame_buffer_watcher.cpp
@@ -27,12 +27,12 @@
 
 #include <android-base/logging.h>
 #include "host/frontend/vnc_server/vnc_utils.h"
-#include "host/libs/screen_connector/screen_connector.h"
 
 using cuttlefish::vnc::FrameBufferWatcher;
 
-FrameBufferWatcher::FrameBufferWatcher(BlackBoard* bb)
-    : bb_{bb}, hwcomposer{bb_} {
+FrameBufferWatcher::FrameBufferWatcher(BlackBoard* bb,
+                                       ScreenConnector& screen_connector)
+    : bb_{bb}, hwcomposer{bb_, screen_connector} {
   for (auto& stripes_vec : stripes_) {
     std::generate_n(std::back_inserter(stripes_vec),
                     SimulatedHWComposer::NumberOfStripes(),
diff --git a/host/frontend/vnc_server/frame_buffer_watcher.h b/host/frontend/vnc_server/frame_buffer_watcher.h
index d147642..9b559b6 100644
--- a/host/frontend/vnc_server/frame_buffer_watcher.h
+++ b/host/frontend/vnc_server/frame_buffer_watcher.h
@@ -22,16 +22,18 @@
 #include <utility>
 #include <vector>
 
-#include "common/libs/threads/thread_annotations.h"
+#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);
+  explicit FrameBufferWatcher(BlackBoard* bb,
+                              ScreenConnector& screen_connector);
   FrameBufferWatcher(const FrameBufferWatcher&) = delete;
   FrameBufferWatcher& operator=(const FrameBufferWatcher&) = delete;
   ~FrameBufferWatcher();
@@ -72,7 +74,7 @@
   mutable std::mutex m_;
   bool closed_ GUARDED_BY(m_){};
   BlackBoard* bb_{};
-  SimulatedHWComposer hwcomposer{bb_};
+  SimulatedHWComposer hwcomposer;
 };
 
 }  // namespace vnc
diff --git a/host/frontend/vnc_server/main.cpp b/host/frontend/vnc_server/main.cpp
index 58c1da6..e036b40 100644
--- a/host/frontend/vnc_server/main.cpp
+++ b/host/frontend/vnc_server/main.cpp
@@ -15,21 +15,40 @@
  */
 
 #include <algorithm>
+#include <memory>
 #include <string>
 
 #include <gflags/gflags.h>
 
+#include "host/frontend/vnc_server/simulated_hw_composer.h"
 #include "host/frontend/vnc_server/vnc_server.h"
 #include "host/frontend/vnc_server/vnc_utils.h"
 #include "host/libs/config/logging.h"
+#include "host/libs/confui/host_mode_ctrl.h"
+#include "host/libs/confui/host_server.h"
 
 DEFINE_bool(agressive, false, "Whether to use agressive server");
+DEFINE_int32(frame_server_fd, -1, "");
 DEFINE_int32(port, 6444, "Port where to listen for connections");
 
 int main(int argc, char* argv[]) {
   cuttlefish::DefaultSubprocessLogging(argv);
   google::ParseCommandLineFlags(&argc, &argv, true);
 
-  cuttlefish::vnc::VncServer vnc_server(FLAGS_port, FLAGS_agressive);
+  auto& host_mode_ctrl = cuttlefish::HostModeCtrl::Get();
+  auto screen_connector_ptr = cuttlefish::vnc::ScreenConnector::Get(
+      FLAGS_frame_server_fd, host_mode_ctrl);
+  auto& screen_connector = *(screen_connector_ptr.get());
+
+  // create confirmation UI service, giving host_mode_ctrl and
+  // screen_connector
+  // keep this singleton object alive until the webRTC process ends
+  static auto& host_confui_server =
+      cuttlefish::confui::HostServer::Get(host_mode_ctrl, screen_connector);
+
+  host_confui_server.Start();
+  // lint does not like the spelling of "agressive", so needs NOTYPO
+  cuttlefish::vnc::VncServer vnc_server(FLAGS_port, FLAGS_agressive,  // NOTYPO
+                                        screen_connector, host_confui_server);
   vnc_server.MainLoop();
 }
diff --git a/host/frontend/vnc_server/simulated_hw_composer.cpp b/host/frontend/vnc_server/simulated_hw_composer.cpp
index ec1df09..b3176b9 100644
--- a/host/frontend/vnc_server/simulated_hw_composer.cpp
+++ b/host/frontend/vnc_server/simulated_hw_composer.cpp
@@ -16,25 +16,23 @@
 
 #include "host/frontend/vnc_server/simulated_hw_composer.h"
 
-#include <gflags/gflags.h>
-
 #include "host/frontend/vnc_server/vnc_utils.h"
 #include "host/libs/config/cuttlefish_config.h"
 
-DEFINE_int32(frame_server_fd, -1, "");
-
 using cuttlefish::vnc::SimulatedHWComposer;
+using ScreenConnector = cuttlefish::vnc::ScreenConnector;
 
-SimulatedHWComposer::SimulatedHWComposer(BlackBoard* bb)
+SimulatedHWComposer::SimulatedHWComposer(BlackBoard* bb,
+                                         ScreenConnector& screen_connector)
     :
 #ifdef FUZZ_TEST_VNC
       engine_{std::random_device{}()},
 #endif
       bb_{bb},
       stripes_(kMaxQueueElements, &SimulatedHWComposer::EraseHalfOfElements),
-      screen_connector_(ScreenConnector::Get(FLAGS_frame_server_fd)) {
+      screen_connector_(screen_connector) {
   stripe_maker_ = std::thread(&SimulatedHWComposer::MakeStripes, this);
-  screen_connector_->SetCallback(std::move(GetScreenConnectorCallback()));
+  screen_connector_.SetCallback(std::move(GetScreenConnectorCallback()));
 }
 
 SimulatedHWComposer::~SimulatedHWComposer() {
@@ -77,21 +75,21 @@
 SimulatedHWComposer::GetScreenConnectorCallback() {
   return [](std::uint32_t display_number, std::uint8_t* frame_pixels,
             cuttlefish::vnc::VncScProcessedFrame& processed_frame) {
+    processed_frame.display_number_ = display_number;
     // TODO(171305898): handle multiple displays.
     if (display_number != 0) {
       processed_frame.is_success_ = false;
       return;
     }
     const std::uint32_t display_w =
-        SimulatedHWComposer::ScreenConnector::ScreenWidth(display_number);
+        ScreenConnector::ScreenWidth(display_number);
     const std::uint32_t display_h =
-        SimulatedHWComposer::ScreenConnector::ScreenHeight(display_number);
+        ScreenConnector::ScreenHeight(display_number);
     const std::uint32_t display_stride_bytes =
-        SimulatedHWComposer::ScreenConnector::ScreenStrideBytes(display_number);
-    const std::uint32_t display_bpp =
-        SimulatedHWComposer::ScreenConnector::BytesPerPixel();
+        ScreenConnector::ScreenStrideBytes(display_number);
+    const std::uint32_t display_bpp = ScreenConnector::BytesPerPixel();
     const std::uint32_t display_size_bytes =
-        SimulatedHWComposer::ScreenConnector::ScreenSizeInBytes(display_number);
+        ScreenConnector::ScreenSizeInBytes(display_number);
 
     auto& raw_screen = processed_frame.raw_screen_;
     raw_screen.assign(frame_pixels, frame_pixels + display_size_bytes);
@@ -137,12 +135,12 @@
    * callback should be set before the first WaitForAtLeastOneClientConnection()
    * (b/178504150) and the first OnFrameAfter().
    */
-  if (!screen_connector_->IsCallbackSet()) {
+  if (!screen_connector_.IsCallbackSet()) {
     LOG(FATAL) << "ScreenConnector callback hasn't been set before MakeStripes";
   }
   while (!closed()) {
     bb_->WaitForAtLeastOneClientConnection();
-    auto sim_hw_processed_frame = screen_connector_->OnNextFrame();
+    auto sim_hw_processed_frame = screen_connector_.OnNextFrame();
     // sim_hw_processed_frame has display number from the guest
     if (!sim_hw_processed_frame.is_success_) {
       continue;
@@ -174,5 +172,5 @@
 int SimulatedHWComposer::NumberOfStripes() { return kNumStripes; }
 
 void SimulatedHWComposer::ReportClientsConnected() {
-  screen_connector_->ReportClientsConnected(true);
+  screen_connector_.ReportClientsConnected(true);
 }
diff --git a/host/frontend/vnc_server/simulated_hw_composer.h b/host/frontend/vnc_server/simulated_hw_composer.h
index 2b8abf1..9d62e3e 100644
--- a/host/frontend/vnc_server/simulated_hw_composer.h
+++ b/host/frontend/vnc_server/simulated_hw_composer.h
@@ -24,35 +24,20 @@
 #include <thread>
 #include <deque>
 
-#include "common/libs/thread_safe_queue/thread_safe_queue.h"
-#include "common/libs/threads/thread_annotations.h"
+#include "common/libs/concurrency/thread_annotations.h"
+#include "common/libs/concurrency/thread_safe_queue.h"
 #include "host/frontend/vnc_server/blackboard.h"
-#include "host/libs/screen_connector/screen_connector.h"
 #include "host/frontend/vnc_server/vnc_utils.h"
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/screen_connector/screen_connector.h"
 
 namespace cuttlefish {
 namespace vnc {
-/**
- * ScreenConnectorImpl will generate this, and enqueue
- *
- * It's basically a (processed) frame, so it:
- *   must be efficiently std::move-able
- * Also, for the sake of algorithm simplicity:
- *   must be default-constructable & assignable
- *
- */
-struct VncScProcessedFrame : public ScreenConnectorFrameInfo {
-  Message raw_screen_;
-  std::deque<Stripe> stripes_;
-};
-
 class SimulatedHWComposer {
  public:
-  using ScreenConnector = ScreenConnector<VncScProcessedFrame>;
   using GenerateProcessedFrameCallback = ScreenConnector::GenerateProcessedFrameCallback;
 
-  SimulatedHWComposer(BlackBoard* bb);
+  SimulatedHWComposer(BlackBoard* bb, ScreenConnector& screen_connector);
   SimulatedHWComposer(const SimulatedHWComposer&) = delete;
   SimulatedHWComposer& operator=(const SimulatedHWComposer&) = delete;
   ~SimulatedHWComposer();
@@ -83,7 +68,7 @@
   BlackBoard* bb_{};
   ThreadSafeQueue<Stripe> stripes_;
   std::thread stripe_maker_;
-  std::unique_ptr<ScreenConnector> screen_connector_;
+  ScreenConnector& screen_connector_;
 };
 }  // namespace vnc
 }  // namespace cuttlefish
diff --git a/host/frontend/vnc_server/virtual_inputs.cpp b/host/frontend/vnc_server/virtual_inputs.cpp
index 3143a6d..938210c 100644
--- a/host/frontend/vnc_server/virtual_inputs.cpp
+++ b/host/frontend/vnc_server/virtual_inputs.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "host/frontend/vnc_server/virtual_inputs.h"
+
 #include <gflags/gflags.h>
 #include <android-base/logging.h>
 #include <linux/input.h>
@@ -25,8 +26,10 @@
 #include <thread>
 #include "keysyms.h"
 
-#include <common/libs/fs/shared_select.h>
-#include <host/libs/config/cuttlefish_config.h>
+#include "common/libs/confui/confui.h"
+#include "common/libs/fs/shared_select.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/logging.h"
 
 using cuttlefish::vnc::VirtualInputs;
 
@@ -347,6 +350,61 @@
 
 VirtualInputs::VirtualInputs() { AddKeyMappings(&keymapping_); }
 
-VirtualInputs* VirtualInputs::Get() {
-  return new SocketVirtualInputs();
+/**
+ * Depending on the host mode (e.g. android, confirmation ui(tee), etc)
+ * deliver the inputs to the right input implementation
+ * e.g. ConfUI's input or regular socket based input
+ */
+class VirtualInputDemux : public VirtualInputs {
+ public:
+  VirtualInputDemux(cuttlefish::confui::HostVirtualInput& confui_input)
+      : confui_input_{confui_input} {}
+  virtual ~VirtualInputDemux() = default;
+
+  virtual void GenerateKeyPressEvent(int code, bool down) override;
+  virtual void PressPowerButton(bool down) override;
+  virtual void HandlePointerEvent(bool touch_down, int x, int y) override;
+
+ private:
+  SocketVirtualInputs socket_virtual_input_;
+  cuttlefish::confui::HostVirtualInput& confui_input_;
+};
+
+void VirtualInputDemux::GenerateKeyPressEvent(int code, bool down) {
+  using cuttlefish::confui::DebugLog;
+  // 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);
+    }
+    DebugLog("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) {
+  using cuttlefish::confui::DebugLog;
+  if (confui_input_.IsConfUiActive()) {
+    DebugLog("currently confirmation UI ignores pointer events at", x, y);
+    return;
+  }
+  socket_virtual_input_.HandlePointerEvent(touch_down, x, y);
+}
+
+std::shared_ptr<VirtualInputs> VirtualInputs::Get(
+    cuttlefish::confui::HostVirtualInput& confui_input) {
+  return std::make_shared<VirtualInputDemux>(confui_input);
 }
diff --git a/host/frontend/vnc_server/virtual_inputs.h b/host/frontend/vnc_server/virtual_inputs.h
index c22de15..f30202e 100644
--- a/host/frontend/vnc_server/virtual_inputs.h
+++ b/host/frontend/vnc_server/virtual_inputs.h
@@ -16,17 +16,20 @@
  * limitations under the License.
  */
 
-#include "vnc_utils.h"
-
 #include <map>
+#include <memory>
 #include <mutex>
 
+#include "host/libs/confui/host_virtual_input.h"
+#include "vnc_utils.h"
+
 namespace cuttlefish {
 namespace vnc {
 
 class VirtualInputs {
  public:
-  static VirtualInputs* Get();
+  static std::shared_ptr<VirtualInputs> Get(
+      cuttlefish::confui::HostVirtualInput& confui_input);
 
   virtual ~VirtualInputs() = default;
 
diff --git a/host/frontend/vnc_server/vnc_client_connection.h b/host/frontend/vnc_server/vnc_client_connection.h
index a2c8b70..b26cf86 100644
--- a/host/frontend/vnc_server/vnc_client_connection.h
+++ b/host/frontend/vnc_server/vnc_client_connection.h
@@ -16,8 +16,8 @@
  * limitations under the License.
  */
 
+#include "common/libs/concurrency/thread_annotations.h"
 #include "common/libs/fs/shared_fd.h"
-#include "common/libs/threads/thread_annotations.h"
 
 #include <cstdint>
 #include <memory>
diff --git a/host/frontend/vnc_server/vnc_server.cpp b/host/frontend/vnc_server/vnc_server.cpp
index dd489b8..eff0d57 100644
--- a/host/frontend/vnc_server/vnc_server.cpp
+++ b/host/frontend/vnc_server/vnc_server.cpp
@@ -16,6 +16,8 @@
 
 #include "host/frontend/vnc_server/vnc_server.h"
 
+#include <memory>
+
 #include <android-base/logging.h>
 #include "common/libs/utils/tcp_socket.h"
 #include "host/frontend/vnc_server/blackboard.h"
@@ -27,11 +29,13 @@
 
 using cuttlefish::vnc::VncServer;
 
-VncServer::VncServer(int port, bool aggressive)
+VncServer::VncServer(int port, bool aggressive,
+                     cuttlefish::vnc::ScreenConnector& screen_connector,
+                     cuttlefish::confui::HostVirtualInput& confui_input)
     : server_(port),
-    virtual_inputs_(VirtualInputs::Get()),
-    frame_buffer_watcher_{&bb_},
-    aggressive_{aggressive} {}
+      virtual_inputs_(VirtualInputs::Get(confui_input)),
+      frame_buffer_watcher_{&bb_, screen_connector},
+      aggressive_{aggressive} {}
 
 void VncServer::MainLoop() {
   while (true) {
diff --git a/host/frontend/vnc_server/vnc_server.h b/host/frontend/vnc_server/vnc_server.h
index 3ae37d8..2751fd1 100644
--- a/host/frontend/vnc_server/vnc_server.h
+++ b/host/frontend/vnc_server/vnc_server.h
@@ -28,13 +28,18 @@
 #include "host/frontend/vnc_server/virtual_inputs.h"
 #include "host/frontend/vnc_server/vnc_client_connection.h"
 #include "host/frontend/vnc_server/vnc_utils.h"
+#include "host/libs/confui/host_mode_ctrl.h"
+#include "host/libs/confui/host_virtual_input.h"
+#include "host/libs/screen_connector/screen_connector.h"
 
 namespace cuttlefish {
 namespace vnc {
 
 class VncServer {
  public:
-  explicit VncServer(int port, bool aggressive);
+  explicit VncServer(int port, bool aggressive,
+                     ScreenConnector& screen_connector,
+                     cuttlefish::confui::HostVirtualInput& confui_input);
 
   VncServer(const VncServer&) = delete;
   VncServer& operator=(const VncServer&) = delete;
@@ -47,8 +52,10 @@
   void StartClientThread(ClientSocket sock);
 
   ServerSocket server_;
+
   std::shared_ptr<VirtualInputs> virtual_inputs_;
   BlackBoard bb_;
+
   FrameBufferWatcher frame_buffer_watcher_;
   bool aggressive_{};
 };
diff --git a/host/frontend/vnc_server/vnc_utils.h b/host/frontend/vnc_server/vnc_utils.h
index 34df434..7ec19f7 100644
--- a/host/frontend/vnc_server/vnc_utils.h
+++ b/host/frontend/vnc_server/vnc_utils.h
@@ -18,12 +18,14 @@
 
 #include <array>
 #include <cstdint>
+#include <memory>
 #include <utility>
 #include <vector>
 
 #include "common/libs/utils/size_utils.h"
 #include "common/libs/utils/tcp_socket.h"
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/screen_connector/screen_connector.h"
 
 namespace cuttlefish {
 namespace vnc {
@@ -63,5 +65,26 @@
   ScreenOrientation orientation{};
 };
 
+/**
+ * ScreenConnectorImpl will generate this, and enqueue
+ *
+ * It's basically a (processed) frame, so it:
+ *   must be efficiently std::move-able
+ * Also, for the sake of algorithm simplicity:
+ *   must be default-constructable & assignable
+ *
+ */
+struct VncScProcessedFrame : public ScreenConnectorFrameInfo {
+  Message raw_screen_;
+  std::deque<Stripe> stripes_;
+  std::unique_ptr<VncScProcessedFrame> Clone() {
+    VncScProcessedFrame* cloned_frame = new VncScProcessedFrame();
+    cloned_frame->raw_screen_ = raw_screen_;
+    cloned_frame->stripes_ = stripes_;
+    return std::unique_ptr<VncScProcessedFrame>(cloned_frame);
+  }
+};
+using ScreenConnector = cuttlefish::ScreenConnector<VncScProcessedFrame>;
+
 }  // namespace vnc
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc/Android.bp b/host/frontend/webrtc/Android.bp
index e0890ad..7ee350f 100644
--- a/host/frontend/webrtc/Android.bp
+++ b/host/frontend/webrtc/Android.bp
@@ -83,6 +83,7 @@
     header_libs: [
         "webrtc_signaling_headers",
         "libwebrtc_absl_headers",
+        "libcuttlefish_confui_host_headers",
     ],
     static_libs: [
         "libwebrtc_absl_base",
@@ -103,6 +104,11 @@
         "libcuttlefish_screen_connector",
         "libcuttlefish_utils",
         "libcuttlefish_wayland_server",
+        "libcuttlefish_confui",
+        "libcuttlefish_confui_host",
+        "libft2.nodep",
+        "libteeui",
+        "libteeui_localization",
         "libdrm",
         "libevent",
         "libffi",
diff --git a/host/frontend/webrtc/connection_observer.cpp b/host/frontend/webrtc/connection_observer.cpp
index a8613e6..090be3b 100644
--- a/host/frontend/webrtc/connection_observer.cpp
+++ b/host/frontend/webrtc/connection_observer.cpp
@@ -30,6 +30,7 @@
 #include <android-base/logging.h>
 #include <gflags/gflags.h>
 
+#include "common/libs/confui/confui.h"
 #include "common/libs/fs/shared_buf.h"
 #include "host/frontend/webrtc/adb_handler.h"
 #include "host/frontend/webrtc/kernel_log_events_handler.h"
@@ -38,6 +39,10 @@
 
 DECLARE_bool(write_virtio_input);
 
+// LOG(DEBUG) for confirmation UI debugging
+// that stands for LOG(DEBUG) << "ConfUI: " << ...
+using cuttlefish::confui::DebugLog;
+
 namespace cuttlefish {
 
 // TODO (b/147511234): de-dup this from vnc server and here
@@ -88,19 +93,24 @@
   }
 }
 
-class ConnectionObserverImpl
+/**
+ * connection observer implementation for regular android mode.
+ * i.e. when it is not in the confirmation UI mode (or TEE),
+ * the control flow will fall back to this ConnectionObserverForAndroid
+ */
+class ConnectionObserverForAndroid
     : public cuttlefish::webrtc_streaming::ConnectionObserver {
  public:
-  ConnectionObserverImpl(cuttlefish::InputSockets& input_sockets,
-                         cuttlefish::SharedFD kernel_log_events_fd,
-                         std::map<std::string, cuttlefish::SharedFD>
-                             commands_to_custom_action_servers,
-                         std::weak_ptr<DisplayHandler> display_handler)
+  ConnectionObserverForAndroid(cuttlefish::InputSockets &input_sockets,
+                               cuttlefish::SharedFD kernel_log_events_fd,
+                               std::map<std::string, cuttlefish::SharedFD>
+                                   commands_to_custom_action_servers,
+                               std::weak_ptr<DisplayHandler> display_handler)
       : input_sockets_(input_sockets),
         kernel_log_events_client_(kernel_log_events_fd),
         commands_to_custom_action_servers_(commands_to_custom_action_servers),
         weak_display_handler_(display_handler) {}
-  virtual ~ConnectionObserverImpl() {
+  virtual ~ConnectionObserverForAndroid() {
     auto display_handler = weak_display_handler_.lock();
     if (display_handler) {
       display_handler->DecClientCount();
@@ -258,9 +268,9 @@
         // invert the lid_switch_open value that is sent to the input device.
         OnSwitchEvent(SW_LID, !evt["lid_switch_open"].asBool());
       }
-      // TODO(b/181157794) Propagate hinge angle sensor data.
       if (evt.isMember("hinge_angle_value")) {
-        LOG(WARNING) << "Hinge angle sensor is not yet implemented.";
+        // TODO(b/181157794) Propagate hinge angle sensor data using a custom
+        // Sensor HAL.
       }
       return;
     }
@@ -305,19 +315,106 @@
   std::set<int32_t> active_touch_slots_;
 };
 
+class ConnectionObserverDemuxer
+    : public cuttlefish::webrtc_streaming::ConnectionObserver {
+ public:
+  ConnectionObserverDemuxer(
+      /* params for the base class */
+      cuttlefish::InputSockets &input_sockets,
+      cuttlefish::SharedFD kernel_log_events_fd,
+      std::map<std::string, cuttlefish::SharedFD>
+          commands_to_custom_action_servers,
+      std::weak_ptr<DisplayHandler> display_handler,
+      /* params for this class */
+      cuttlefish::confui::HostVirtualInput &confui_input)
+      : android_input_(input_sockets, kernel_log_events_fd,
+                       commands_to_custom_action_servers, display_handler),
+        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()) {
+      DebugLog("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()) {
+      DebugLog("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:
+          DebugLog("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);
+  }
+
+ private:
+  ConnectionObserverForAndroid android_input_;
+  cuttlefish::confui::HostVirtualInput &confui_input_;
+};
+
 CfConnectionObserverFactory::CfConnectionObserverFactory(
-    cuttlefish::InputSockets& input_sockets,
-    cuttlefish::SharedFD kernel_log_events_fd)
+    cuttlefish::InputSockets &input_sockets,
+    cuttlefish::SharedFD kernel_log_events_fd,
+    cuttlefish::confui::HostVirtualInput &confui_input)
     : input_sockets_(input_sockets),
-      kernel_log_events_fd_(kernel_log_events_fd) {}
+      kernel_log_events_fd_(kernel_log_events_fd),
+      confui_input_{confui_input} {}
 
 std::shared_ptr<cuttlefish::webrtc_streaming::ConnectionObserver>
 CfConnectionObserverFactory::CreateObserver() {
   return std::shared_ptr<cuttlefish::webrtc_streaming::ConnectionObserver>(
-      new ConnectionObserverImpl(input_sockets_,
-                                 kernel_log_events_fd_,
-                                 commands_to_custom_action_servers_,
-                                 weak_display_handler_));
+      new ConnectionObserverDemuxer(input_sockets_, kernel_log_events_fd_,
+                                    commands_to_custom_action_servers_,
+                                    weak_display_handler_, confui_input_));
 }
 
 void CfConnectionObserverFactory::AddCustomActionServer(
diff --git a/host/frontend/webrtc/connection_observer.h b/host/frontend/webrtc/connection_observer.h
index 9ec1e49..b3ede7d 100644
--- a/host/frontend/webrtc/connection_observer.h
+++ b/host/frontend/webrtc/connection_observer.h
@@ -22,6 +22,7 @@
 #include "common/libs/fs/shared_fd.h"
 #include "host/frontend/webrtc/display_handler.h"
 #include "host/frontend/webrtc/lib/connection_observer.h"
+#include "host/libs/confui/host_virtual_input.h"
 
 namespace cuttlefish {
 
@@ -37,8 +38,10 @@
 class CfConnectionObserverFactory
     : public cuttlefish::webrtc_streaming::ConnectionObserverFactory {
  public:
-  CfConnectionObserverFactory(cuttlefish::InputSockets& input_sockets,
-                              cuttlefish::SharedFD kernel_log_events_fd);
+  CfConnectionObserverFactory(
+      cuttlefish::InputSockets& input_sockets,
+      cuttlefish::SharedFD kernel_log_events_fd,
+      cuttlefish::confui::HostVirtualInput& confui_input);
   ~CfConnectionObserverFactory() override = default;
 
   std::shared_ptr<cuttlefish::webrtc_streaming::ConnectionObserver> CreateObserver()
@@ -55,6 +58,7 @@
   std::map<std::string, cuttlefish::SharedFD>
       commands_to_custom_action_servers_;
   std::weak_ptr<DisplayHandler> weak_display_handler_;
+  cuttlefish::confui::HostVirtualInput& confui_input_;
 };
 
 }  // namespace cuttlefish
diff --git a/host/frontend/webrtc/display_handler.cpp b/host/frontend/webrtc/display_handler.cpp
index 2b4f574..c24ce9d 100644
--- a/host/frontend/webrtc/display_handler.cpp
+++ b/host/frontend/webrtc/display_handler.cpp
@@ -25,9 +25,9 @@
 namespace cuttlefish {
 DisplayHandler::DisplayHandler(
     std::shared_ptr<webrtc_streaming::VideoSink> display_sink,
-    std::unique_ptr<ScreenConnector> screen_connector)
-    : display_sink_(display_sink), screen_connector_(std::move(screen_connector)) {
-  screen_connector_->SetCallback(std::move(GetScreenConnectorCallback()));
+    ScreenConnector& screen_connector)
+    : display_sink_(display_sink), screen_connector_(screen_connector) {
+  screen_connector_.SetCallback(std::move(GetScreenConnectorCallback()));
 }
 
 DisplayHandler::GenerateProcessedFrameCallback DisplayHandler::GetScreenConnectorCallback() {
@@ -35,6 +35,7 @@
     DisplayHandler::GenerateProcessedFrameCallback callback =
         [](std::uint32_t display_number, std::uint8_t* frame_pixels,
            WebRtcScProcessedFrame& processed_frame) {
+          processed_frame.display_number_ = display_number;
           // TODO(171305898): handle multiple displays.
           if (display_number != 0) {
             processed_frame.is_success_ = false;
@@ -61,7 +62,7 @@
 
 [[noreturn]] void DisplayHandler::Loop() {
   for (;;) {
-    auto processed_frame = screen_connector_->OnNextFrame();
+    auto processed_frame = screen_connector_.OnNextFrame();
     // processed_frame has display number from the guest
     {
       std::lock_guard<std::mutex> lock(last_buffer_mutex_);
@@ -101,14 +102,14 @@
 void DisplayHandler::IncClientCount() {
   client_count_++;
   if (client_count_ == 1) {
-    screen_connector_->ReportClientsConnected(true);
+    screen_connector_.ReportClientsConnected(true);
   }
 }
 
 void DisplayHandler::DecClientCount() {
   client_count_--;
   if (client_count_ == 0) {
-    screen_connector_->ReportClientsConnected(false);
+    screen_connector_.ReportClientsConnected(false);
   }
 }
 
diff --git a/host/frontend/webrtc/display_handler.h b/host/frontend/webrtc/display_handler.h
index 3045e07..aed38a1 100644
--- a/host/frontend/webrtc/display_handler.h
+++ b/host/frontend/webrtc/display_handler.h
@@ -33,9 +33,17 @@
  *   must be default-constructable & assignable
  *
  */
-struct WebRtcScProcessedFrame : ScreenConnectorFrameInfo {
+struct WebRtcScProcessedFrame : public ScreenConnectorFrameInfo {
   // must support move semantic
   std::unique_ptr<CvdVideoFrameBuffer> buf_;
+  std::unique_ptr<WebRtcScProcessedFrame> Clone() {
+    // copy internal buffer, not move
+    CvdVideoFrameBuffer* new_buffer = new CvdVideoFrameBuffer(*(buf_.get()));
+    auto cloned_frame = std::make_unique<WebRtcScProcessedFrame>();
+    cloned_frame->buf_ =
+        std::move(std::unique_ptr<CvdVideoFrameBuffer>(new_buffer));
+    return std::move(cloned_frame);
+  }
 };
 
 class DisplayHandler {
@@ -43,9 +51,8 @@
   using ScreenConnector = cuttlefish::ScreenConnector<WebRtcScProcessedFrame>;
   using GenerateProcessedFrameCallback = ScreenConnector::GenerateProcessedFrameCallback;
 
-  DisplayHandler(
-      std::shared_ptr<webrtc_streaming::VideoSink> display_sink,
-      std::unique_ptr<ScreenConnector> screen_connector);
+  DisplayHandler(std::shared_ptr<webrtc_streaming::VideoSink> display_sink,
+                 ScreenConnector& screen_connector);
   ~DisplayHandler() = default;
 
   [[noreturn]] void Loop();
@@ -57,7 +64,7 @@
  private:
   GenerateProcessedFrameCallback GetScreenConnectorCallback();
   std::shared_ptr<webrtc_streaming::VideoSink> display_sink_;
-  std::unique_ptr<ScreenConnector> screen_connector_;
+  ScreenConnector& screen_connector_;
   std::shared_ptr<webrtc_streaming::VideoFrameBuffer> last_buffer_;
   std::mutex last_buffer_mutex_;
   std::mutex next_frame_mutex_;
diff --git a/host/frontend/webrtc/main.cpp b/host/frontend/webrtc/main.cpp
index cdc9563..ce40e1c 100644
--- a/host/frontend/webrtc/main.cpp
+++ b/host/frontend/webrtc/main.cpp
@@ -37,6 +37,8 @@
 #include "host/libs/audio_connector/server.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/logging.h"
+#include "host/libs/confui/host_mode_ctrl.h"
+#include "host/libs/confui/host_server.h"
 #include "host/libs/screen_connector/screen_connector.h"
 
 DEFINE_int32(touch_fd, -1, "An fd to listen on for touch connections.");
@@ -173,8 +175,16 @@
 
   auto cvd_config = cuttlefish::CuttlefishConfig::Get();
   auto instance = cvd_config->ForDefaultInstance();
-  auto screen_connector =
-      cuttlefish::DisplayHandler::ScreenConnector::Get(FLAGS_frame_server_fd);
+  auto& host_mode_ctrl = cuttlefish::HostModeCtrl::Get();
+  auto screen_connector_ptr = cuttlefish::DisplayHandler::ScreenConnector::Get(
+      FLAGS_frame_server_fd, host_mode_ctrl);
+  auto& screen_connector = *(screen_connector_ptr.get());
+
+  // create confirmation UI service, giving host_mode_ctrl and
+  // screen_connector
+  // keep this singleton object alive until the webRTC process ends
+  static auto& host_confui_server =
+      cuttlefish::confui::HostServer::Get(host_mode_ctrl, screen_connector);
 
   StreamerConfig streamer_config;
 
@@ -195,16 +205,16 @@
   }
 
   auto observer_factory = std::make_shared<CfConnectionObserverFactory>(
-      input_sockets, kernel_log_events_client);
+      input_sockets, kernel_log_events_client, host_confui_server);
 
   auto streamer = Streamer::Create(streamer_config, observer_factory);
   CHECK(streamer) << "Could not create streamer";
 
   auto display_0 = streamer->AddDisplay(
-      "display_0", screen_connector->ScreenWidth(0),
-      screen_connector->ScreenHeight(0), cvd_config->dpi(), true);
-  auto display_handler =
-    std::make_shared<DisplayHandler>(display_0, std::move(screen_connector));
+      "display_0", screen_connector.ScreenWidth(0),
+      screen_connector.ScreenHeight(0), cvd_config->dpi(), true);
+  auto display_handler = std::shared_ptr<DisplayHandler>(
+      new DisplayHandler(display_0, screen_connector));
 
   std::unique_ptr<cuttlefish::webrtc_streaming::LocalRecorder> local_recorder;
   if (cvd_config->record_screen()) {
@@ -340,6 +350,7 @@
   if (audio_handler) {
     audio_handler->Start();
   }
+  host_confui_server.Start();
   display_handler->Loop();
 
   return 0;
diff --git a/host/frontend/webrtc_operator/assets/controls.css b/host/frontend/webrtc_operator/assets/controls.css
index 1971399..5bb0cce 100644
--- a/host/frontend/webrtc_operator/assets/controls.css
+++ b/host/frontend/webrtc_operator/assets/controls.css
@@ -18,7 +18,7 @@
   padding-left: 2px;
   padding-right: 7px;
   border-radius: 10px;
-  background-color: #434343;
+  background-color: #5f6368; /* Google grey 700 */
   width: 117px;
   height: 64px;
 }
diff --git a/host/frontend/webrtc_operator/assets/index.html b/host/frontend/webrtc_operator/assets/index.html
index cdbf549..7ab6a72 100644
--- a/host/frontend/webrtc_operator/assets/index.html
+++ b/host/frontend/webrtc_operator/assets/index.html
@@ -25,45 +25,42 @@
     </head>
 
     <body>
-      <section id='device_selector'>
-        <h1>Available devices <span id='refresh_list'>&#8635;</span></h1>
-        <ul id="device_list"></ul>
+      <section id='device-selector'>
+        <h1>Available devices <span id='refresh-list'>&#8635;</span></h1>
+        <ul id="device-list"></ul>
       </section>
-      <section id='device_connection'>
-        <div id='control_view'>
-          <div id='app_controls'>
-            <h2 class="section-title">App Controls</h2>
-            <div id="keyboardCaptureCtrl" title="Capture Keyboard"></div>
-            <div id="micCaptureCtrl" title="Capture Microphone"></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>
+            <audio autoplay controls id="device-audio"></audio>
           </div>
-          <hr>
-          <div id='control_panel'>
-            <h2 class="section-title">Device Controls</h2>
-            <div id='control_panel_default_buttons'></div>
-            <h2 class="section-title" id='custom_controls_title'>Custom Controls</h2>
-            <div id='control_panel_custom_buttons'></div>
-          </div>
-          <h3 id='adb_status_message' class='connecting'>Awaiting bootup and adb connection. Please wait...</h3>
-          <div id='device_details'>
-            <h2 class="section-title">Device Details</h2>
-            <h3>Hardware Configuration</h3>
-            <span id='device_details_hardware'>unknown</span>
-          </div>
-          <div id='device_speakers'>
-            <h2 class="section-title">Device Speakers</h2>
-            <audio autoplay controls id="deviceAudio"></audio>
+          <div id='status-div'>
+            <h3 id='status-message' class='connecting'>Connecting to device</h3>
           </div>
         </div>
-        <div id='device_view'>
-          <h3 id='device_status_message'></h3>
-          <video id="deviceScreen" autoplay ></video>
-          <h3 id='webrtc_status_message'>
-            No connection to the guest device.<br><br>
-            Please ensure the WebRTC process on the host machine is active.<br><br>
-            Tip: <code>ps aux | grep -i webrtc</code>
-          </h3>
+        <div id='controls-and-screens'>
+          <div id='control-panel-default-buttons' class='control-panel-column'>
+            <button id='device-details-button' title='Device Details' class='material-icons'>
+              settings
+            </button>
+          </div>
+          <div id='control-panel-custom-buttons' class='control-panel-column'></div>
+          <div id='screens'>
+            <video id="device-screen" autoplay ></video>
+          </div>
         </div>
       </section>
+      <div id='device-details-modal'>
+        <div id='device-details-modal-header'>
+          <h2>Device Details</h2>
+          <button id='device-details-close' title='Close' class='material-icons'>close</button>
+        </div>
+        <hr>
+        <h3>Hardware Configuration</h3>
+        <span id='device-details-hardware'>unknown</span>
+      </div>
 
        <script src="js/adb.js"></script>
        <script src="js/cf_webrtc.js" type="module"></script>
diff --git a/host/frontend/webrtc_operator/assets/js/app.js b/host/frontend/webrtc_operator/assets/js/app.js
index 3bc6a3c..61a9bf3 100644
--- a/host/frontend/webrtc_operator/assets/js/app.js
+++ b/host/frontend/webrtc_operator/assets/js/app.js
@@ -18,38 +18,41 @@
 
 function ConnectToDevice(device_id) {
   console.log('ConnectToDevice ', device_id);
-  const keyboardCaptureCtrl = document.getElementById('keyboardCaptureCtrl');
+  const keyboardCaptureCtrl = document.getElementById('keyboard-capture-control');
   createToggleControl(keyboardCaptureCtrl, "keyboard", onKeyboardCaptureToggle);
-  const micCaptureCtrl = document.getElementById('micCaptureCtrl');
+  const micCaptureCtrl = document.getElementById('mic-capture-control');
   createToggleControl(micCaptureCtrl, "mic", onMicCaptureToggle);
+  // TODO(b/163867676): Enable the microphone control when the audio stream is
+  // injected into the guest. Until then, control is disabled.
+  micCaptureCtrl.style.display = 'none';
 
-  const deviceScreen = document.getElementById('deviceScreen');
-  const deviceAudio = document.getElementById('deviceAudio');
-  const deviceView = document.getElementById('device_view');
-  const webrtcStatusMessage = document.getElementById('webrtc_status_message');
-  const adbStatusMessage = document.getElementById('adb_status_message');
+  const deviceScreen = document.getElementById('device-screen');
+  const deviceAudio = document.getElementById('device-audio');
+  const statusMessage = document.getElementById('status-message');
 
-  const deviceStatusMessage = document.getElementById('device_status_message');
   let connectionAttemptDuration = 0;
   const intervalMs = 500;
   let deviceStatusEllipsisCount = 0;
   let animateDeviceStatusMessage = setInterval(function() {
-    deviceStatusEllipsisCount = (deviceStatusEllipsisCount + 1) % 4;
-    deviceStatusMessage.textContent = 'Connecting to device'
-        + '.'.repeat(deviceStatusEllipsisCount);
-
     connectionAttemptDuration += intervalMs;
     if (connectionAttemptDuration > 30000) {
-      deviceStatusMessage.textContent += '\r\n\r\nConnection should have occurred by now.'
-          + '\r\nPlease attempt to restart the guest device.'
-    } else if (connectionAttemptDuration > 15000) {
-      deviceStatusMessage.textContent += '\r\n\r\nConnection is taking longer than expected...'
+      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);
 
   deviceScreen.addEventListener('loadeddata', (evt) => {
     clearInterval(animateDeviceStatusMessage);
-    deviceStatusMessage.style.display = 'none';
+    statusMessage.textContent = 'Awaiting bootup and adb connection. Please wait...';
     resizeDeviceView();
     deviceScreen.style.visibility = 'visible';
     // Enable the buttons after the screen is visible.
@@ -77,11 +80,11 @@
     // Certain default adb buttons change screen state, so wait for boot
     // completion before enabling these buttons.
     if (adbConnected && bootCompleted) {
-      adbStatusMessage.className = 'connected';
-      adbStatusMessage.textContent =
+      statusMessage.className = 'connected';
+      statusMessage.textContent =
           'bootup and adb connection established successfully.';
       setTimeout(function() {
-        adbStatusMessage.style.visibility = 'hidden';
+        statusMessage.style.visibility = 'hidden';
       }, 5000);
       for (const [_, button] of Object.entries(buttons)) {
         if (button.adb) {
@@ -99,9 +102,9 @@
           showBootCompletion();
         },
         function() {
-          adbStatusMessage.className = 'error';
-          adbStatusMessage.textContent = 'adb connection failed.';
-          adbStatusMessage.style.visibility = 'visible';
+          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;
@@ -147,11 +150,12 @@
     }
   }
 
+  const screensDiv = document.getElementById('screens');
   function resizeDeviceView() {
     // Auto-scale the screen based on window size.
     // Max window width of 70%, allowing space for the control panel.
-    let ww = window.innerWidth * 0.7;
-    let wh = window.innerHeight;
+    let ww = screensDiv.offsetWidth * 0.7;
+    let wh = screensDiv.offsetHeight;
     let vw = currentDisplayDetails.x_res;
     let vh = currentDisplayDetails.y_res;
     let scaling = vw * wh > vh * ww ? ww / vw : wh / vh;
@@ -166,19 +170,12 @@
       deviceScreen.style.width = vh * scaling;
       deviceScreen.style.height = vw * scaling;
     }
-
-    // Set the deviceView size so that the control panel positions itself next
-    // to the screen correctly.
-    deviceView.style.width = currentRotation == 0 ? deviceScreen.style.width :
-                                                    deviceScreen.style.height;
-    deviceView.style.height = currentRotation == 0 ? deviceScreen.style.height :
-                                                     deviceScreen.style.width;
   }
   window.onresize = resizeDeviceView;
 
   function createControlPanelButton(command, title, icon_name,
       listener=onControlPanelButton,
-      parent_id='control_panel_default_buttons') {
+      parent_id='control-panel-default-buttons') {
     let button = document.createElement('button');
     document.getElementById(parent_id).appendChild(button);
     button.title = title;
@@ -207,14 +204,36 @@
   createControlPanelButton('volumedown', 'Volume Down', 'volume_down');
   createControlPanelButton('volumeup', 'Volume Up', 'volume_up');
 
+  const deviceDetailsModal = document.getElementById('device-details-modal');
+  const deviceDetailsButton = document.getElementById('device-details-button');
+  const deviceDetailsClose = document.getElementById('device-details-close');
+  function showHideDeviceDetailsModal(show) {
+    // Position the modal to the right of the device details button.
+    deviceDetailsModal.style.top = deviceDetailsButton.offsetTop;
+    deviceDetailsModal.style.left = deviceDetailsButton.offsetWidth + 30;
+    if (show) {
+      deviceDetailsModal.style.display = 'block';
+    } else {
+      deviceDetailsModal.style.display = 'none';
+    }
+  }
+  // Allow the device details button to toggle the modal,
+  deviceDetailsButton.addEventListener('click',
+      evt => showHideDeviceDetailsModal(deviceDetailsModal.style.display != 'block'));
+  // but the close button always closes.
+  deviceDetailsClose.addEventListener('click',
+      evt => showHideDeviceDetailsModal(false));
+
   let options = {
     wsUrl: ((location.protocol == 'http:') ? 'ws://' : 'wss://') +
       location.host + '/connect_client',
   };
 
   function showWebrtcError() {
-    webrtcStatusMessage.style.display = 'block';
-    deviceStatusMessage.style.display = 'none';
+    statusMessage.className = 'error';
+    statusMessage.textContent = 'No connection to the guest device. ' +
+        'Please ensure the WebRTC process on the host machine is active.';
+    statusMessage.style.visibility = 'visible';
     deviceScreen.style.display = 'none';
     for (const [_, button] of Object.entries(buttons)) {
       button.button.disabled = true;
@@ -243,26 +262,32 @@
       startMouseTracking();  // TODO stopMouseTracking() when disconnected
       updateDeviceHardwareDetails(deviceConnection.description.hardware);
       updateDeviceDisplayDetails(deviceConnection.description.displays[0]);
-      if (deviceConnection.description.custom_control_panel_buttons.length == 0) {
-        document.getElementById('custom_controls_title').style.visibility = 'hidden';
-      } else {
+      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');
+                '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');
+                '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');
+                'control-panel-custom-buttons');
           }
         }
       }
@@ -294,7 +319,7 @@
   let hardwareDetailsText = '';
   let displayDetailsText = '';
   function updateDeviceDetailsText() {
-    document.getElementById('device_details_hardware').textContent = [
+    document.getElementById('device-details-hardware').textContent = [
       hardwareDetailsText,
       displayDetailsText,
     ].join('\n');
@@ -348,7 +373,7 @@
     initializeAdb();
     if (e.type == 'mousedown') {
       adbShell(
-          '/vendor/bin/cuttlefish_rotate ' +
+          '/vendor/bin/cuttlefish_sensor_injection rotate ' +
           (currentRotation == 0 ? 'landscape' : 'portrait'))
     }
   }
@@ -376,6 +401,13 @@
         };
         deviceConnection.sendControlMessage(JSON.stringify(message));
         console.log(JSON.stringify(message));
+        // TODO(b/181157794): Use a custom Sensor HAL for hinge_angle injection
+        // instead of this guest binary.
+        if ('hinge_angle_value' in states[index]) {
+          adbShell(
+              '/vendor/bin/cuttlefish_sensor_injection hinge_angle ' +
+              states[index].hinge_angle_value);
+        }
         // Cycle to the next state.
         index = (index + 1) % states.length;
       }
@@ -608,14 +640,14 @@
 function ConnectDeviceCb(dev_id) {
   console.log('Connect: ' + dev_id);
   // Hide the device selection screen
-  document.getElementById('device_selector').style.display = 'none';
+  document.getElementById('device-selector').style.display = 'none';
   // Show the device control screen
-  document.getElementById('device_connection').style.visibility = 'visible';
+  document.getElementById('device-connection').style.visibility = 'visible';
   ConnectToDevice(dev_id);
 }
 
 function ShowNewDeviceList(device_ids) {
-  let ul = document.getElementById('device_list');
+  let ul = document.getElementById('device-list');
   ul.innerHTML = "";
   let count = 1;
   let device_to_button_map = {};
@@ -649,5 +681,5 @@
 // Get any devices that are already connected
 UpdateDeviceList();
 // Update the list at the user's request
-document.getElementById('refresh_list')
+document.getElementById('refresh-list')
     .addEventListener('click', evt => UpdateDeviceList());
diff --git a/host/frontend/webrtc_operator/assets/style.css b/host/frontend/webrtc_operator/assets/style.css
index d1ec542..ca4e7b2 100644
--- a/host/frontend/webrtc_operator/assets/style.css
+++ b/host/frontend/webrtc_operator/assets/style.css
@@ -15,136 +15,160 @@
  */
 
 body {
-    background-color:black;
-    margin: 0;
+  background-color:black;
+  margin: 0;
 }
 
-#device_selector {
-    color: whitesmoke;
+#device-selector {
+  color: whitesmoke;
 }
-#device_selector li.device_entry {
-    cursor: pointer;
+#device-selector li.device-entry {
+  cursor: pointer;
 }
 
-#refresh_list {
-    cursor: pointer;
+#refresh-list {
+  cursor: pointer;
 }
 
-#device_list .device_entry button {
-    margin-left: 10px;
+#device-list .device-entry button {
+  margin-left: 10px;
+}
+
+#device-connection {
+  visibility: hidden;
+  max-height: 100vh;
 }
 
 
-#device_connection {
-    visibility: hidden;
+/* Top header row. */
+
+#header {
+  height: 64px;
+  margin-top: 10px;
+  margin-bottom: 10px;
+  /* Items inside this use a row Flexbox.*/
+  display: flex;
+  align-items: center;
 }
 
-#device_view {
-    transition: width 1s;
+#app-controls {
+  margin-left: 10px;
 }
-#control_view {
-    text-align: left;
-    padding: 10px;
-    min-width: 450px;
+#app-controls > div {
+  display: inline-block;
+  position: relative;
+  margin-right: 6px;
 }
-/* Screens >1000px place the device view on the left and the control
- * view on the right. Other screens have deview view on top and control
- * view on bottom. */
-@media screen and (min-width: 1000px) {
-    #device_view {
-        float: left;
-    }
-    #control_view {
-        float: left;
-    }
+#device-audio {
+  margin-bottom: 5px;
 }
 
-#deviceScreen {
-    touch-action: none;
-    transform-origin: top right;
-    object-fit: cover;
+#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;
 }
 
-body {
-    font-family: 'Open Sans', sans-serif;
+/* Control panel buttons and device screen(s). */
+
+#controls-and-screens {
+  height: calc(100% - 84px);
+
+  /* Items inside this use a row Flexbox.*/
+  display: flex;
+}
+#controls-and-screens div {
+  margin-left: 10px;
+  margin-right: 10px;
 }
 
-h2.section-title {
-    color: white;
-}
-
-#app_controls > div {
-    display: inline-block;
-    position: relative;
-    margin-right: 6px;
-}
-
-#device_view h3 {
-    padding: 30px;
-    font-family: 'Open Sans', sans-serif;
-    color: white;
-}
-
-#device_status_message {
-    white-space: pre-line;
-    width: 400px;
-}
-
-#webrtc_status_message {
-    text-align: center;
-    /* White text on dark red background. */
-    background-color: #900000;
-    /* Hidden by default. */
-    display: none;
-}
-
-#device_connection button {
-    height: 60px;
-    min-width: 60px;
-    display: inline-block;
-    margin: 10px 10px;
-    font-size: 48px;
-    border-radius: 8px;
-    border: none;
-    background-color: #dbdbdb;
-    transition-duration: 0.2s;
-}
-#control_panel button:hover {
-    background-color: #c9c9c9;
-}
-
-#adb_status_message {
-    padding: 10px;
-    font-family: 'Open Sans', sans-serif;
-    color: white;
-}
-#adb_status_message.connecting {
-    /* dark yellow */
-    background-color: #927836;
-}
-#adb_status_message.error {
-    /* dark red */
-    background-color: #900000;
-}
-#adb_status_message.connected {
-    /* dark green */
-    background-color: #007000;
-}
-
-#device_details {
-    font-family: 'Open Sans', sans-serif;
-    color: white;
-    background-color: #383838;
-    margin-top: 20px;
-    padding: 20px;
-    padding-top: 1px;
-}
-#device_details span {
-    white-space: pre;
-}
-
-/* This effectively disables the microphone control. It should be enabled again
- * when the audio stream is injected into the guest */
-div#micCaptureCtrl {
+#device-details-modal {
+  /* Start out hidden, and use absolute positioning. */
   display: none;
+  position: absolute;
+
+  border-radius: 16px;
+  padding: 20px;
+  padding-top: 1px;
+
+  background-color: #5f6368ea; /* Semi-transparent Google grey 500 */
+  color: white;
+  font-family: 'Open Sans', sans-serif;
+}
+#device-details-modal-header {
+  /* Items inside this use a row Flexbox.*/
+  display: flex;
+  justify-content: space-between;
+}
+#device-details-close {
+  color: white;
+  border: none;
+  outline: none;
+  background-color: transparent;
+}
+#device-details-modal span {
+  white-space: pre;
+}
+
+.control-panel-column {
+  width: 80px;
+  /* 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: 16px;
+}
+
+.control-panel-column button {
+  margin: 10px;
+  height: 60px;
+  font-size: 48px;
+
+  color: #e8eaed; /* Google grey 200 */
+  border: none;
+  outline: none;
+  background-color: transparent;
+}
+.control-panel-column button:disabled {
+  color: #9aa0a6; /* Google grey 500 */
+}
+#device-details-button {
+  margin: 0px;
+  height: 80px;
+  border-radius: 16px;
+  background-color: #5f6368; /* Google grey 700 */
+}
+
+#screens {
+  /* Take up the remaining width of the window.*/
+  flex-grow: 1;
+  /* Don't grow taller than the window.*/
+  max-height: 100vh;
+}
+
+#device-screen {
+  touch-action: none;
+  transform-origin: top right;
+  object-fit: cover;
 }
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index 54b5c1c..2aafc84 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -432,6 +432,8 @@
 
     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;
diff --git a/host/libs/config/cuttlefish_config_instance.cpp b/host/libs/config/cuttlefish_config_instance.cpp
index 6ea73e8..9bd8c8a 100644
--- a/host/libs/config/cuttlefish_config_instance.cpp
+++ b/host/libs/config/cuttlefish_config_instance.cpp
@@ -169,6 +169,11 @@
   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"));
 }
diff --git a/host/libs/confui/host_server.h b/host/libs/confui/host_server.h
index 95cc3ac..05164c9 100644
--- a/host/libs/confui/host_server.h
+++ b/host/libs/confui/host_server.h
@@ -28,9 +28,9 @@
 #include <android-base/logging.h>
 #include <teeui/utils.h>
 
+#include "common/libs/concurrency/semaphore.h"
 #include "common/libs/confui/confui.h"
 #include "common/libs/fs/shared_fd.h"
-#include "common/libs/semaphore/semaphore.h"
 #include "host/commands/kernel_log_monitor/utils.h"
 #include "host/libs/config/logging.h"
 #include "host/libs/confui/host_mode_ctrl.h"
diff --git a/host/libs/confui/multiplexer.h b/host/libs/confui/multiplexer.h
index c0869d0..2ac1928 100644
--- a/host/libs/confui/multiplexer.h
+++ b/host/libs/confui/multiplexer.h
@@ -22,8 +22,8 @@
 #include <mutex>
 #include <thread>
 
-#include "common/libs/semaphore/semaphore.h"
-#include "common/libs/thread_safe_queue/thread_safe_queue.h"
+#include "common/libs/concurrency/semaphore.h"
+#include "common/libs/concurrency/thread_safe_queue.h"
 
 namespace cuttlefish {
 namespace confui {
diff --git a/host/libs/image_aggregator/Android.bp b/host/libs/image_aggregator/Android.bp
new file mode 100644
index 0000000..491a777
--- /dev/null
+++ b/host/libs/image_aggregator/Android.bp
@@ -0,0 +1,53 @@
+//
+// 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_library {
+    name: "libcdisk_spec",
+    srcs: [
+        "cdisk_spec.proto",
+    ],
+    proto: {
+        type: "full",
+        export_proto_headers: true,
+        include_dirs: [
+            "external/protobuf/src",
+        ],
+    },
+    defaults: ["cuttlefish_host"],
+}
+
+cc_library_static {
+    name: "libimage_aggregator",
+    srcs: [
+        "image_aggregator.cc",
+    ],
+    shared_libs: [
+        "libcuttlefish_fs",
+        "libcuttlefish_utils",
+        "libbase",
+        "libprotobuf-cpp-full",
+        "libz",
+    ],
+    static_libs: [
+        "libcdisk_spec",
+        "libext2_uuid",
+        "libsparse",
+    ],
+    defaults: ["cuttlefish_host"],
+}
diff --git a/host/commands/assemble_cvd/cdisk_spec.proto b/host/libs/image_aggregator/cdisk_spec.proto
similarity index 100%
rename from host/commands/assemble_cvd/cdisk_spec.proto
rename to host/libs/image_aggregator/cdisk_spec.proto
diff --git a/host/commands/assemble_cvd/image_aggregator.cc b/host/libs/image_aggregator/image_aggregator.cc
similarity index 87%
rename from host/commands/assemble_cvd/image_aggregator.cc
rename to host/libs/image_aggregator/image_aggregator.cc
index 46a3f82..8d4027a 100644
--- a/host/commands/assemble_cvd/image_aggregator.cc
+++ b/host/libs/image_aggregator/image_aggregator.cc
@@ -18,7 +18,7 @@
  * GUID Partition Table and Composite Disk generation code.
  */
 
-#include "host/commands/assemble_cvd/image_aggregator.h"
+#include "host/libs/image_aggregator/image_aggregator.h"
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -29,8 +29,9 @@
 #include <string>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/logging.h>
-#include <json/json.h>
+#include <cdisk_spec.pb.h>
 #include <google/protobuf/text_format.h>
 #include <sparse/sparse.h>
 #include <uuid.h>
@@ -41,9 +42,7 @@
 #include "common/libs/utils/files.h"
 #include "common/libs/utils/size_utils.h"
 #include "common/libs/utils/subprocess.h"
-#include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/mbr.h"
-#include "device/google/cuttlefish/host/commands/assemble_cvd/cdisk_spec.pb.h"
 
 namespace cuttlefish {
 namespace {
@@ -124,7 +123,8 @@
 
 struct PartitionInfo {
   ImagePartition source;
-  std::uint64_t size;
+  std::uint64_t guest_size;
+  std::uint64_t host_size;
   std::uint64_t offset;
 };
 
@@ -192,15 +192,19 @@
   CompositeDiskBuilder() : next_disk_offset_(sizeof(GptBeginning)) {}
 
   void AppendDisk(ImagePartition source) {
-    auto size = AlignToPowerOf2(UnsparsedSize(source.image_file_path),
-                                PARTITION_SIZE_SHIFT);
-    partitions_.push_back(PartitionInfo {
-      .source = source,
-      .size = size,
-      .offset = next_disk_offset_,
+    auto host_size = UnsparsedSize(source.image_file_path);
+    auto guest_size = AlignToPowerOf2(host_size, PARTITION_SIZE_SHIFT);
+    CHECK(host_size == guest_size || source.read_only)
+        << "read-write file " << source.image_file_path
+        << " 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,
+        .offset = next_disk_offset_,
     });
-    next_disk_offset_ = AlignToPowerOf2(next_disk_offset_ + size,
-                                        PARTITION_SIZE_SHIFT);
+    next_disk_offset_ =
+        AlignToPowerOf2(next_disk_offset_ + guest_size, PARTITION_SIZE_SHIFT);
   }
 
   std::uint64_t DiskSize() const {
@@ -220,18 +224,32 @@
     disk.set_length(DiskSize());
 
     ComponentDisk* header = disk.add_component_disks();
-    header->set_file_path(header_file);
+    header->set_file_path(AbsolutePath(header_file));
     header->set_offset(0);
 
     for (auto& partition : partitions_) {
       ComponentDisk* component = disk.add_component_disks();
-      component->set_file_path(partition.source.image_file_path);
+      component->set_file_path(AbsolutePath(partition.source.image_file_path));
       component->set_offset(partition.offset);
-      component->set_read_write_capability(ReadWriteCapability::READ_WRITE);
+      component->set_read_write_capability(
+          partition.source.read_only ? ReadWriteCapability::READ_ONLY
+                                     : ReadWriteCapability::READ_WRITE);
+      // When partition's size differs from its size on the host
+      // 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) {
+        ComponentDisk* component = disk.add_component_disks();
+        component->set_file_path(AbsolutePath(header_file));
+        component->set_offset(partition.offset + partition.host_size);
+        component->set_read_write_capability(ReadWriteCapability::READ_ONLY);
+      }
     }
 
     ComponentDisk* footer = disk.add_component_disks();
-    footer->set_file_path(footer_file);
+    footer->set_file_path(AbsolutePath(footer_file));
     footer->set_offset(next_disk_offset_);
 
     return disk;
@@ -267,9 +285,10 @@
     uuid_generate(gpt.header.disk_guid);
     for (std::size_t i = 0; i < partitions_.size(); i++) {
       const auto& partition = partitions_[i];
-      gpt.entries[i] = GptPartitionEntry {
-        .first_lba = partition.offset / SECTOR_SIZE,
-        .last_lba = (partition.offset + partition.size) / SECTOR_SIZE - 1,
+      gpt.entries[i] = GptPartitionEntry{
+          .first_lba = partition.offset / SECTOR_SIZE,
+          .last_lba =
+              (partition.offset + partition.guest_size) / SECTOR_SIZE - 1,
       };
       uuid_generate(gpt.entries[i].unique_partition_guid);
       if (uuid_parse(GetPartitionGUID(partition.source),
diff --git a/host/commands/assemble_cvd/image_aggregator.h b/host/libs/image_aggregator/image_aggregator.h
similarity index 98%
rename from host/commands/assemble_cvd/image_aggregator.h
rename to host/libs/image_aggregator/image_aggregator.h
index 7c5719b..97bc13b 100644
--- a/host/commands/assemble_cvd/image_aggregator.h
+++ b/host/libs/image_aggregator/image_aggregator.h
@@ -33,6 +33,7 @@
   std::string label;
   std::string image_file_path;
   ImagePartitionType type;
+  bool read_only;
 };
 
 /**
diff --git a/host/libs/screen_connector/Android.bp b/host/libs/screen_connector/Android.bp
index fcfc747..d4e4002 100644
--- a/host/libs/screen_connector/Android.bp
+++ b/host/libs/screen_connector/Android.bp
@@ -28,10 +28,18 @@
         "libjsoncpp",
         "liblog",
     ],
+    header_libs: [
+        "libcuttlefish_confui_host_headers",
+    ],
     static_libs: [
         "libcuttlefish_host_config",
         "libcuttlefish_utils",
+        "libcuttlefish_confui",
         "libcuttlefish_wayland_server",
+        "libcuttlefish_confui_host",
+        "libft2.nodep",
+        "libteeui",
+        "libteeui_localization",
     ],
     defaults: ["cuttlefish_host"],
 }
diff --git a/host/libs/screen_connector/screen_connector.h b/host/libs/screen_connector/screen_connector.h
index 232eea0..6cc6b34 100644
--- a/host/libs/screen_connector/screen_connector.h
+++ b/host/libs/screen_connector/screen_connector.h
@@ -17,21 +17,33 @@
 #pragma once
 
 #include <cassert>
+#include <chrono>
 #include <cstdint>
 #include <functional>
 #include <memory>
 #include <mutex>
+#include <optional>
 #include <thread>
 #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_queue.h"
-#include "host/libs/screen_connector/screen_connector_ctrl.h"
 #include "host/libs/screen_connector/wayland_screen_connector.h"
 
+// LOG(X) for confirmation UI debugging
+using cuttlefish::confui::DebugLog;
+using cuttlefish::confui::ErrorLog;
+using cuttlefish::confui::FatalLog;
+
 namespace cuttlefish {
 
 template <typename ProcessedFrameType>
@@ -60,14 +72,15 @@
       /* ScImpl enqueues this type into the Q */
       ProcessedFrameType& msg)>;
 
-  static std::unique_ptr<ScreenConnector<ProcessedFrameType>> Get(const int frames_fd) {
+  static std::unique_ptr<ScreenConnector<ProcessedFrameType>> Get(
+      const int frames_fd, HostModeCtrl& host_mode_ctrl) {
     auto config = cuttlefish::CuttlefishConfig::Get();
     ScreenConnector<ProcessedFrameType>* raw_ptr = nullptr;
     if (config->gpu_mode() == cuttlefish::kGpuModeDrmVirgl ||
         config->gpu_mode() == cuttlefish::kGpuModeGfxStream ||
         config->gpu_mode() == cuttlefish::kGpuModeGuestSwiftshader) {
       raw_ptr = new ScreenConnector<ProcessedFrameType>(
-          std::make_unique<WaylandScreenConnector>(frames_fd));
+          std::make_unique<WaylandScreenConnector>(frames_fd), host_mode_ctrl);
     } else {
       LOG(FATAL) << "Invalid gpu mode: " << config->gpu_mode();
     }
@@ -84,14 +97,14 @@
   void SetCallback(GenerateProcessedFrameCallback&& frame_callback) {
     std::lock_guard<std::mutex> lock(streamer_callback_mutex_);
     callback_from_streamer_ = std::move(frame_callback);
+    streamer_callback_set_cv_.notify_all();
     /*
      * the first WaitForAtLeastOneClientConnection() call from VNC requires the
      * Android-frame-processing thread starts beforehands (b/178504150)
      */
-    if (!is_frame_fetching_thread_started_) {
-      is_frame_fetching_thread_started_ = true;
-      sc_android_impl_fetcher_ =
-          std::move(std::thread(&ScreenConnector::FrameFetchingLoop, this));
+    if (!sc_android_frame_fetching_thread_.joinable()) {
+      sc_android_frame_fetching_thread_ = cuttlefish::confui::thread::RunThread(
+          "AndroidFetcher", &ScreenConnector::AndroidFrameFetchingLoop, this);
     }
   }
 
@@ -108,39 +121,68 @@
    * NOTE THAT THIS IS THE ONLY CONSUMER OF THE TWO QUEUES
    */
   ProcessedFrameType OnNextFrame() {
-    // sc_ctrl has a semaphore internally
-    // passing beyond SemWait means either queue has an item
-    sc_ctrl_.SemWaitItem();
-    return sc_android_queue_.PopFront();
-
-    // TODO: add confirmation ui
-    /*
-     * if (!sc_android_queue_.Empty()) return sc_android_queue_.PopFront();
-     * else return conf_ui_queue.PopFront();
-     */
+    on_next_frame_cnt_++;
+    while (true) {
+      DebugLog("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();
+      DebugLog("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) {
+          DebugLog("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.
+        DebugLog("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;
+      }
+      DebugLog("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();
+    }
   }
 
-  [[noreturn]] void FrameFetchingLoop() {
+  [[noreturn]] void AndroidFrameFetchingLoop() {
+    unsigned long long int loop_cnt = 0;
+    cuttlefish::confui::thread::Set("AndroidFrameFetcher",
+                                    std::this_thread::get_id());
     while (true) {
-      sc_ctrl_.WaitAndroidMode( /* pass method to stop sc_android_impl_ */);
-      /*
-       * TODO: instead of WaitAndroidMode,
-       * we could sc_android_impl_->OnFrameAfter but enqueue it only in AndroidMode
-       */
-      ProcessedFrameType msg;
+      loop_cnt++;
+      ProcessedFrameType processed_frame;
       decltype(callback_from_streamer_) cp_of_streamer_callback;
       {
         std::lock_guard<std::mutex> lock(streamer_callback_mutex_);
         cp_of_streamer_callback = callback_from_streamer_;
       }
       GenerateProcessedFrameCallbackImpl callback_for_sc_impl =
-          std::bind(cp_of_streamer_callback,
-                    std::placeholders::_1, std::placeholders::_2,
-                    std::ref(msg));
-      bool flag = sc_android_impl_->OnNextFrame(callback_for_sc_impl);
-      msg.is_success_ = flag && msg.is_success_;
-      auto result = ProcessedFrameType{std::move(msg)};
-      sc_android_queue_.PushBack(std::move(result));
+          std::bind(cp_of_streamer_callback, std::placeholders::_1,
+                    std::placeholders::_2, std::ref(processed_frame));
+      DebugLog(cuttlefish::confui::thread::GetName(std::this_thread::get_id()),
+               " calling Android OnNextFrame. ", " at loop #", loop_cnt);
+      bool flag = sc_android_src_->OnNextFrame(callback_for_sc_impl);
+      processed_frame.is_success_ = flag && processed_frame.is_success_;
+      const bool is_confui_mode = host_mode_ctrl_.IsConfirmatioUiMode();
+      if (!is_confui_mode) {
+        DebugLog(
+            cuttlefish::confui::thread::GetName(std::this_thread::get_id()),
+            " is sending an Android Frame at loop_cnt #", loop_cnt);
+        sc_android_queue_.PushBack(std::move(processed_frame));
+        continue;
+      }
+      DebugLog(cuttlefish::confui::thread::GetName(std::this_thread::get_id()),
+               " is skipping an Android Frame at loop_cnt #", loop_cnt);
     }
   }
 
@@ -151,14 +193,29 @@
    * Android guest frames if Confirmation UI HAL is not active.
    *
    */
-  bool RenderConfirmationUi(const std::uint32_t, std::uint8_t*) override {
+  bool RenderConfirmationUi(const std::uint32_t display,
+                            std::uint8_t* raw_frame) override {
+    render_confui_cnt_++;
+    // wait callback is not set, the streamer is not ready
+    // return with LOG(ERROR)
+    if (!IsCallbackSet()) {
+      ErrorLog("callback function to process frames is not yet set");
+      return false;
+    }
+    ProcessedFrameType processed_frame;
+    auto this_thread_name = cuttlefish::confui::thread::GetName();
+    DebugLog(this_thread_name, " is sending a #", render_confui_cnt_,
+             " Conf UI frame");
+    callback_from_streamer_(display, raw_frame, processed_frame);
+    // now add processed_frame to the queue
+    sc_confui_queue_.PushBack(std::move(processed_frame));
     return true;
   }
 
   // Let the screen connector know when there are clients connected
   void ReportClientsConnected(bool have_clients) {
     // screen connector implementation must implement ReportClientsConnected
-    sc_android_impl_->ReportClientsConnected(have_clients);
+    sc_android_src_->ReportClientsConnected(have_clients);
     return ;
   }
 
@@ -166,20 +223,28 @@
   template <typename T,
             typename = std::enable_if_t<
                 std::is_base_of<ScreenConnectorSource, T>::value, void>>
-  ScreenConnector(std::unique_ptr<T>&& impl)
-      : sc_android_impl_{std::move(impl)},
-        is_frame_fetching_thread_started_(false),
-        sc_android_queue_(sc_ctrl_) {}
+  ScreenConnector(std::unique_ptr<T>&& impl, HostModeCtrl& host_mode_ctrl)
+      : sc_android_src_{std::move(impl)},
+        host_mode_ctrl_{host_mode_ctrl},
+        on_next_frame_cnt_{0},
+        render_confui_cnt_{0},
+        sc_android_queue_{sc_sem_},
+        sc_confui_queue_{sc_sem_} {}
   ScreenConnector() = delete;
 
  private:
-  std::unique_ptr<ScreenConnectorSource> sc_android_impl_; // either socket_based or wayland
-  bool is_frame_fetching_thread_started_;
-  ScreenConnectorCtrl sc_ctrl_;
+  // either socket_based or wayland
+  std::unique_ptr<ScreenConnectorSource> sc_android_src_;
+  HostModeCtrl& host_mode_ctrl_;
+  unsigned long long int on_next_frame_cnt_;
+  unsigned long long int render_confui_cnt_;
+  Semaphore sc_sem_;
   ScreenConnectorQueue<ProcessedFrameType> sc_android_queue_;
+  ScreenConnectorQueue<ProcessedFrameType> sc_confui_queue_;
   GenerateProcessedFrameCallback callback_from_streamer_;
-  std::thread sc_android_impl_fetcher_;
+  std::thread sc_android_frame_fetching_thread_;
   std::mutex streamer_callback_mutex_; // mutex to set & read callback_from_streamer_
+  std::condition_variable streamer_callback_set_cv_;
 };
 
 }  // namespace cuttlefish
diff --git a/host/libs/screen_connector/screen_connector_ctrl.h b/host/libs/screen_connector/screen_connector_ctrl.h
index 3fed1df..9b90152 100644
--- a/host/libs/screen_connector/screen_connector_ctrl.h
+++ b/host/libs/screen_connector/screen_connector_ctrl.h
@@ -23,7 +23,7 @@
 #include <mutex>
 #include <thread>
 
-#include "common/libs/semaphore/semaphore.h"
+#include "common/libs/concurrency/semaphore.h"
 
 namespace cuttlefish {
 /**
@@ -42,9 +42,7 @@
     kConfUI_Mode
   };
 
-  ScreenConnectorCtrl()
-      : atomic_mode_(ModeType::kAndroidMode)
-  {}
+  ScreenConnectorCtrl() : atomic_mode_(ModeType::kAndroidMode) {}
 
   /**
    * The thread that enqueues Android frames will call this to wait until
@@ -89,14 +87,10 @@
     return ret_val;
   }
 
-  void SemWaitItem() {
-    sem_.SemWait();
-  }
+  void SemWait() { sem_.SemWait(); }
 
   // Only called by the producers
-  void SemPostItem() {
-    sem_.SemPost();
-  }
+  void SemPost() { sem_.SemPost(); }
 
  private:
   std::mutex mode_mtx_;
diff --git a/host/libs/screen_connector/screen_connector_queue.h b/host/libs/screen_connector/screen_connector_queue.h
index f8a76f3..2019168 100644
--- a/host/libs/screen_connector/screen_connector_queue.h
+++ b/host/libs/screen_connector/screen_connector_queue.h
@@ -23,7 +23,7 @@
 #include <condition_variable>
 #include <chrono>
 
-#include "host/libs/screen_connector/screen_connector_ctrl.h"
+#include "common/libs/concurrency/semaphore.h"
 
 namespace cuttlefish {
 // move-based concurrent queue
@@ -36,10 +36,8 @@
   static_assert( is_movable<T>::value,
                  "Items in ScreenConnectorQueue should be std::mov-able");
 
-  ScreenConnectorQueue(ScreenConnectorCtrl& sc_ctrl_)
-      : q_mutex_(std::make_unique<std::mutex>()),
-        global_item_tracker_(sc_ctrl_)
-  {}
+  ScreenConnectorQueue(Semaphore& sc_sem)
+      : q_mutex_(std::make_unique<std::mutex>()), sc_semaphore_(sc_sem) {}
   ScreenConnectorQueue(ScreenConnectorQueue&& cq) = delete;
   ScreenConnectorQueue(const ScreenConnectorQueue& cq) = delete;
   ScreenConnectorQueue& operator=(const ScreenConnectorQueue& cq) = delete;
@@ -93,13 +91,13 @@
      * This IS intended to awake the screen_connector consumer thread
      * when one or more items are available at least in one queue
      */
-    global_item_tracker_.SemPostItem();
+    sc_semaphore_.SemPost();
   }
   void PushBack(T& item) = delete;
   void PushBack(const T& item) = delete;
 
   /*
-   * PopFront must be preceded by global_item_tracker_.SemWaitItem()
+   * PopFront must be preceded by sc_semaphore_.SemWaitItem()
    *
    */
   T PopFront() {
@@ -121,7 +119,7 @@
   std::deque<T> buffer_;
   std::unique_ptr<std::mutex> q_mutex_;
   std::condition_variable q_empty_;
-  ScreenConnectorCtrl& global_item_tracker_;
+  Semaphore& sc_semaphore_;
 };
 
 } // namespace cuttlefish
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index e2ec717..4f136fd 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -20,7 +20,6 @@
 #include <sys/types.h>
 
 #include <cassert>
-#include <iomanip>
 #include <string>
 #include <vector>
 
@@ -140,10 +139,7 @@
   // 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
-    std::stringstream stream;
-    stream << std::setfill('0') << std::setw(2) << std::hex
-           << 1 + VmManager::kDefaultNumHvcs + VmManager::kMaxDisks - num_disks;
-    return "androidboot.boot_devices=pci0000:00/0000:00:" + stream.str() + ".0";
+    return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 1, 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
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index ed61c69..f26818a 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -25,7 +25,6 @@
 #include <unistd.h>
 
 #include <cstdlib>
-#include <iomanip>
 #include <sstream>
 #include <string>
 #include <thread>
@@ -125,12 +124,7 @@
     case Arch::X86:
     case Arch::X86_64: {
       // QEMU has additional PCI devices for an ISA bridge and PIIX4
-      std::stringstream stream;
-      stream << std::setfill('0') << std::setw(2) << std::hex
-             << 2 + VmManager::kDefaultNumHvcs + VmManager::kMaxDisks -
-                    num_disks;
-      return "androidboot.boot_devices=pci0000:00/0000:00:" + stream.str() +
-             ".0";
+      return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 2, num_disks);
     }
     case Arch::Arm:
       return "androidboot.boot_devices=3f000000.pcie";
diff --git a/host/libs/vm_manager/vm_manager.cpp b/host/libs/vm_manager/vm_manager.cpp
index 3a5a41d..8c2ef57 100644
--- a/host/libs/vm_manager/vm_manager.cpp
+++ b/host/libs/vm_manager/vm_manager.cpp
@@ -16,13 +16,14 @@
 
 #include "host/libs/vm_manager/vm_manager.h"
 
-#include <memory>
-
 #include <android-base/logging.h>
 
+#include <iomanip>
+#include <memory>
+
 #include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/vm_manager/qemu_manager.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
+#include "host/libs/vm_manager/qemu_manager.h"
 
 namespace cuttlefish {
 namespace vm_manager {
@@ -45,6 +46,21 @@
   return vmm;
 }
 
+std::string ConfigureMultipleBootDevices(const std::string& pci_path,
+                                         int pci_offset, int num_disks) {
+  int num_boot_devices =
+      (num_disks < VmManager::kDefaultNumBootDevices) ? num_disks : VmManager::kDefaultNumBootDevices;
+  std::string boot_devices_prop = "androidboot.boot_devices=";
+  for (int i = 0; i < num_boot_devices; i++) {
+    std::stringstream stream;
+    stream << std::setfill('0') << std::setw(2) << std::hex
+           << pci_offset + i + VmManager::kDefaultNumHvcs + VmManager::kMaxDisks - num_disks;
+    boot_devices_prop += pci_path + stream.str() + ".0,";
+  }
+  boot_devices_prop.pop_back();
+  return {boot_devices_prop};
+}
+
 } // namespace vm_manager
 } // namespace cuttlefish
 
diff --git a/host/libs/vm_manager/vm_manager.h b/host/libs/vm_manager/vm_manager.h
index 2af2846..b538e8f 100644
--- a/host/libs/vm_manager/vm_manager.h
+++ b/host/libs/vm_manager/vm_manager.h
@@ -51,6 +51,13 @@
   // assigned virtual disk PCI ID (i.e. 2 disks = 7 hvcs, 1 disks = 8 hvcs)
   static const int kMaxDisks = 3;
 
+  // This is the number of virtual disks that contribute to the named partition
+  // list (/dev/block/by-name/*) under Android. The partitions names from
+  // multiple disks *must not* collide. Normally we have one set of partitions
+  // from the powerwashed disk (operating system disk) and another set from
+  // the persistent disk
+  static const int kDefaultNumBootDevices = 2;
+
   VmManager(Arch arch) : arch_(arch) {}
   virtual ~VmManager() = default;
 
@@ -68,6 +75,9 @@
 
 std::unique_ptr<VmManager> GetVmManager(const std::string&, Arch arch);
 
+std::string ConfigureMultipleBootDevices(const std::string& pci_path, int pci_offset,
+                                         int num_disks);
+
 } // namespace vm_manager
 } // namespace cuttlefish
 
diff --git a/shared/config/config_foldable.json b/shared/config/config_foldable.json
index 7a1e731..dd64d17 100644
--- a/shared/config/config_foldable.json
+++ b/shared/config/config_foldable.json
@@ -7,7 +7,8 @@
                 {
                         "device_states": [
                                 {
-                                        "lid_switch_open": false
+                                        "lid_switch_open": false,
+                                        "hinge_angle_value": 0
                                 }
                         ],
                         "button":{
@@ -19,7 +20,8 @@
                 {
                         "device_states": [
                                 {
-                                        "lid_switch_open": true
+                                        "lid_switch_open": true,
+                                        "hinge_angle_value": 180
                                 }
                         ],
                         "button":{
diff --git a/shared/config/ueventd.rc b/shared/config/ueventd.rc
index 84d2806..3d5a219 100644
--- a/shared/config/ueventd.rc
+++ b/shared/config/ueventd.rc
@@ -29,4 +29,4 @@
 /dev/gnss0 0666 system system
 
 # Factory Reset Protection
-/dev/block/vdb 0660 system system
+/dev/block/by-name/frp 0660 system system
diff --git a/shared/device.mk b/shared/device.mk
index 089c662..55325fb 100644
--- a/shared/device.mk
+++ b/shared/device.mk
@@ -32,6 +32,8 @@
 PRODUCT_USE_DYNAMIC_PARTITIONS := true
 DISABLE_RILD_OEM_HOOK := true
 
+PRODUCT_SET_DEBUGFS_RESTRICTIONS := true
+
 PRODUCT_SOONG_NAMESPACES += device/generic/goldfish-opengl # for vulkan
 
 PRODUCT_FS_COMPRESSION := 1
@@ -73,7 +75,7 @@
 
 # Storage: for factory reset protection feature
 PRODUCT_VENDOR_PROPERTIES += \
-    ro.frp.pst=/dev/block/vdb
+    ro.frp.pst=/dev/block/by-name/frp
 
 # Explanation of specific properties:
 #   debug.hwui.swap_with_damage avoids boot failure on M http://b/25152138
@@ -147,7 +149,7 @@
 #
 PRODUCT_PACKAGES += \
     CuttlefishService \
-    cuttlefish_rotate \
+    cuttlefish_sensor_injection \
     rename_netiface \
     setup_wifi \
     bt_vhci_forwarder \
diff --git a/shared/foldable/device_state_configuration.xml b/shared/foldable/device_state_configuration.xml
index b32eced..9618b11 100644
--- a/shared/foldable/device_state_configuration.xml
+++ b/shared/foldable/device_state_configuration.xml
@@ -9,13 +9,21 @@
     </conditions>
   </device-state>
   <device-state>
-    <!-- TODO(b/181583265): Use the hinge sensor for HALF_OPENED state.
-      Currently using an identifier of 3 so that this state never takes
-      precedence over the lower-identifier-value CLOSED and OPENED states,
-      until a hinge sensor is available. -->
-    <identifier>3</identifier>
+    <identifier>1</identifier>
     <name>HALF_OPENED</name>
-    <conditions></conditions>
+    <conditions>
+      <lid-switch>
+        <open>true</open>
+      </lid-switch>
+      <sensor>
+        <type>android.sensor.hinge_angle</type>
+        <name>Hinge Angle Sensor</name>
+        <value>
+          <min>0</min>
+          <max>180</max>
+        </value>
+      </sensor>
+    </conditions>
   </device-state>
   <device-state>
     <identifier>2</identifier>
diff --git a/shared/sepolicy/vendor/cuttlefish_rotate.te b/shared/sepolicy/vendor/cuttlefish_rotate.te
deleted file mode 100644
index 7d8a9a1..0000000
--- a/shared/sepolicy/vendor/cuttlefish_rotate.te
+++ /dev/null
@@ -1,15 +0,0 @@
-type cuttlefish_rotate, domain;
-type cuttlefish_rotate_exec, exec_type, vendor_file_type, file_type;
-
-# Switch to cuttlefish_rotate domain when executing from shell.
-domain_auto_trans(shell, cuttlefish_rotate_exec, cuttlefish_rotate)
-allow cuttlefish_rotate shell:fd use;
-
-# Allow cuttlefish_rotate to communicate over adb connection.
-allow cuttlefish_rotate adbd:fd use;
-allow cuttlefish_rotate adbd:unix_stream_socket { read write };
-# Needed to run the binary directly via adb socket.
-allow cuttlefish_rotate devpts:chr_file { read write };
-
-# Grant cuttlefish_rotate access to the ISensors HAL.
-hal_client_domain(cuttlefish_rotate, hal_sensors)
diff --git a/shared/sepolicy/vendor/cuttlefish_sensor_injection.te b/shared/sepolicy/vendor/cuttlefish_sensor_injection.te
new file mode 100644
index 0000000..9e7aca5
--- /dev/null
+++ b/shared/sepolicy/vendor/cuttlefish_sensor_injection.te
@@ -0,0 +1,15 @@
+type cuttlefish_sensor_injection, domain;
+type cuttlefish_sensor_injection_exec, exec_type, vendor_file_type, file_type;
+
+# Switch to cuttlefish_sensor_injection domain when executing from shell.
+domain_auto_trans(shell, cuttlefish_sensor_injection_exec, cuttlefish_sensor_injection)
+allow cuttlefish_sensor_injection shell:fd use;
+
+# Allow cuttlefish_sensor_injection to communicate over adb connection.
+allow cuttlefish_sensor_injection adbd:fd use;
+allow cuttlefish_sensor_injection adbd:unix_stream_socket { read write };
+# Needed to run the binary directly via adb socket.
+allow cuttlefish_sensor_injection devpts:chr_file { read write };
+
+# Grant cuttlefish_sensor_injection access to the ISensors HAL.
+hal_client_domain(cuttlefish_sensor_injection, hal_sensors)
diff --git a/shared/sepolicy/vendor/file_contexts b/shared/sepolicy/vendor/file_contexts
index 20c3341..b082bae 100644
--- a/shared/sepolicy/vendor/file_contexts
+++ b/shared/sepolicy/vendor/file_contexts
@@ -11,7 +11,8 @@
 /dev/block/by-name/userdata u:object_r:userdata_block_device:s0
 /dev/block/by-name/metadata u:object_r:metadata_block_device:s0
 
-/dev/block/vdb  u:object_r:frp_block_device:s0
+/dev/block/by-name/frp  u:object_r:frp_block_device:s0
+
 /dev/block/pmem0  u:object_r:rebootescrow_device:s0
 /dev/block/zram0  u:object_r:swap_block_device:s0
 /dev/dri u:object_r:gpu_device:s0
@@ -55,7 +56,7 @@
 #############################
 # Vendor files
 #
-/vendor/bin/cuttlefish_rotate   u:object_r:cuttlefish_rotate_exec:s0
+/vendor/bin/cuttlefish_sensor_injection   u:object_r:cuttlefish_sensor_injection_exec:s0
 /vendor/bin/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
diff --git a/shared/sepolicy/vendor/shell.te b/shared/sepolicy/vendor/shell.te
index 60d15fa..cc26032 100644
--- a/shared/sepolicy/vendor/shell.te
+++ b/shared/sepolicy/vendor/shell.te
@@ -1,5 +1,5 @@
 allow shell serial_device:chr_file { getattr ioctl read write };
-allow shell cuttlefish_rotate_exec:file rx_file_perms;
+allow shell cuttlefish_sensor_injection_exec:file rx_file_perms;
 
 # TODO(b/130668487): Label the vsock sockets.
 allow shell adbd:{ socket vsock_socket } rw_socket_perms_no_ioctl;