Merge "Delete cutf hwcomposer" into sc-dev
diff --git a/common/libs/utils/subprocess.cpp b/common/libs/utils/subprocess.cpp
index 089b2e4..c9d676a 100644
--- a/common/libs/utils/subprocess.cpp
+++ b/common/libs/utils/subprocess.cpp
@@ -157,14 +157,6 @@
   }
   return true;
 }
-Command::ParameterBuilder::~ParameterBuilder() { Build(); }
-void Command::ParameterBuilder::Build() {
-  auto param = stream_.str();
-  stream_ = std::stringstream();
-  if (param.size()) {
-    cmd_->AddParameter(param);
-  }
-}
 
 Command::~Command() {
   // Close all inherited file descriptors
diff --git a/common/libs/utils/subprocess.h b/common/libs/utils/subprocess.h
index 7d2c0f7..0eb2bcc 100644
--- a/common/libs/utils/subprocess.h
+++ b/common/libs/utils/subprocess.h
@@ -119,25 +119,6 @@
   }
 
  public:
-  class ParameterBuilder {
-   public:
-    ParameterBuilder(Command* cmd) : cmd_(cmd){};
-    ParameterBuilder(ParameterBuilder&& builder) = default;
-    ~ParameterBuilder();
-
-    template <typename T>
-    ParameterBuilder& operator<<(T t) {
-      cmd_->BuildParameter(&stream_, t);
-      return *this;
-    }
-
-    void Build();
-
-   private:
-    Command* cmd_;
-    std::stringstream stream_;
-  };
-
   // Constructs a command object from the path to an executable binary and an
   // optional subprocess stopper. When not provided, stopper defaults to sending
   // SIGKILL to the subprocess.
@@ -190,8 +171,6 @@
     return false;
   }
 
-  ParameterBuilder GetParameterBuilder() { return ParameterBuilder(this); }
-
   // Redirects the standard IO of the command.
   bool RedirectStdIO(Subprocess::StdIOChannel channel, SharedFD shared_fd);
   bool RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
diff --git a/guest/hals/keymint/remote/remote_keymint_device.cpp b/guest/hals/keymint/remote/remote_keymint_device.cpp
index fbf2568..c9171c8 100644
--- a/guest/hals/keymint/remote/remote_keymint_device.cpp
+++ b/guest/hals/keymint/remote/remote_keymint_device.cpp
@@ -407,6 +407,12 @@
   return kmError2ScopedAStatus(response.error);
 }
 
+ScopedAStatus RemoteKeyMintDevice::convertStorageKeyToEphemeral(
+    const std::vector<uint8_t>& /* storageKeyBlob */,
+    std::vector<uint8_t>* /* ephemeralKeyBlob */) {
+  return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
+}
+
 ScopedAStatus RemoteKeyMintDevice::performOperation(
     const vector<uint8_t>& /* request */, vector<uint8_t>* /* response */) {
   return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
diff --git a/guest/hals/keymint/remote/remote_keymint_device.h b/guest/hals/keymint/remote/remote_keymint_device.h
index c4e840e..7f289b4 100644
--- a/guest/hals/keymint/remote/remote_keymint_device.h
+++ b/guest/hals/keymint/remote/remote_keymint_device.h
@@ -73,6 +73,10 @@
       const optional<TimeStampToken>& timestampToken) override;
   ScopedAStatus earlyBootEnded() override;
 
+  ScopedAStatus convertStorageKeyToEphemeral(
+      const std::vector<uint8_t>& storageKeyBlob,
+      std::vector<uint8_t>* ephemeralKeyBlob) override;
+
   ScopedAStatus performOperation(const vector<uint8_t>& request,
                                  vector<uint8_t>* response) override;
 
diff --git a/guest/hals/ril/reference-libril/ril.h b/guest/hals/ril/reference-libril/ril.h
index 8942bcc..586de42 100644
--- a/guest/hals/ril/reference-libril/ril.h
+++ b/guest/hals/ril/reference-libril/ril.h
@@ -7537,7 +7537,14 @@
  */
 #define RIL_REQUEST_GET_SLICING_CONFIG 169
 
-#define RIL_REQUEST_LAST RIL_REQUEST_GET_SLICING_CONFIG
+#define RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS 170
+
+#define RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY 171
+
+#define RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORDS 172
+
+
+#define RIL_REQUEST_LAST RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORDS
 
 /***********************************************************************/
 
diff --git a/guest/hals/ril/reference-libril/ril_commands.h b/guest/hals/ril/reference-libril/ril_commands.h
index 4cb950b..81629b0 100644
--- a/guest/hals/ril/reference-libril/ril_commands.h
+++ b/guest/hals/ril/reference-libril/ril_commands.h
@@ -183,5 +183,7 @@
     {RIL_REQUEST_SET_DATA_THROTTLING, radio_1_6::setDataThrottlingResponse},
     {RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS, radio_1_6::getSystemSelectionChannelsResponse},
     {RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP, radio_1_6::getAllowedNetworkTypesBitmapResponse},
-    {RIL_REQUEST_GET_SLICING_CONFIG, radio_1_6::getSlicingConfigResponse}
-
+    {RIL_REQUEST_GET_SLICING_CONFIG, radio_1_6::getSlicingConfigResponse},
+    {RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS, radio_1_6::getSimPhonebookRecordsResponse},
+    {RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY, radio_1_6::getSimPhonebookCapacityResponse},
+    {RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORDS, radio_1_6::updateSimPhonebookRecordsResponse}
diff --git a/guest/hals/ril/reference-libril/ril_service.cpp b/guest/hals/ril/reference-libril/ril_service.cpp
index 635d48f..848d4dd 100644
--- a/guest/hals/ril/reference-libril/ril_service.cpp
+++ b/guest/hals/ril/reference-libril/ril_service.cpp
@@ -646,6 +646,11 @@
     Return<void> setCarrierInfoForImsiEncryption_1_6(
             int32_t serial,
             const ::android::hardware::radio::V1_6::ImsiEncryptionInfo& imsiEncryptionInfo);
+    Return<void> getSimPhonebookRecords(int32_t serial);
+    Return<void> getSimPhonebookCapacity(int32_t serial);
+    Return<void> updateSimPhonebookRecords(
+            int32_t serial,
+            const ::android::hardware::radio::V1_6::PhonebookRecordInfo& recordInfo);
 };
 
 struct OemHookImpl : public IOemHook {
@@ -4696,6 +4701,34 @@
     return Void();
 }
 
+
+Return<void> RadioImpl_1_6::getSimPhonebookRecords(int32_t serial) {
+#if VDBG
+    RLOGD("getSimPhonebookRecords: serial %d", serial);
+#endif
+    dispatchVoid(serial, mSlotId, RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS);
+    return Void();
+}
+
+Return<void> RadioImpl_1_6::getSimPhonebookCapacity(int32_t serial) {
+#if VDBG
+    RLOGD("getSimPhonebookCapacity: serial %d", serial);
+#endif
+    dispatchVoid(serial, mSlotId, RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY);
+    return Void();
+}
+
+Return<void> RadioImpl_1_6::updateSimPhonebookRecords(
+    int32_t serial,
+    const ::android::hardware::radio::V1_6::PhonebookRecordInfo& recordInfo) {
+#if VDBG
+    RLOGD("updateSimPhonebookRecords: serial %d", serial);
+#endif
+    dispatchVoid(serial, mSlotId, RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORDS);
+    return Void();
+}
+
+
 // OEM hook methods:
 Return<void> OemHookImpl::setResponseFunctions(
         const ::android::sp<IOemHookResponse>& oemHookResponseParam,
@@ -10303,6 +10336,31 @@
 
     return 0;
 }
+
+int radio_1_6::getSimPhonebookRecordsResponse(int slotId, int responseType, int serial,
+                             RIL_Errno e, void *response, size_t responseLen) {
+#if VDBG
+    RLOGD("getSimPhonebookRecordsResponse: serial %d", serial);
+#endif
+    return 0;
+}
+
+int radio_1_6::getSimPhonebookCapacityResponse(int slotId, int responseType, int serial,
+                             RIL_Errno e, void *response, size_t responseLen) {
+#if VDBG
+    RLOGD("getSimPhonebookRecordsResponse: serial %d", serial);
+#endif
+    return 0;
+}
+
+int radio_1_6::updateSimPhonebookRecordsResponse(int slotId, int responseType, int serial,
+                             RIL_Errno e, void *response, size_t responseLen) {
+#if VDBG
+    RLOGD("getSimPhonebookRecordsResponse: serial %d", serial);
+#endif
+    return 0;
+}
+
 /***************************************************************************************************
  * INDICATION FUNCTIONS
  * The below function handle unsolicited messages coming from the Radio
diff --git a/guest/hals/ril/reference-libril/ril_service.h b/guest/hals/ril/reference-libril/ril_service.h
index 85bd091..30c7b69 100644
--- a/guest/hals/ril/reference-libril/ril_service.h
+++ b/guest/hals/ril/reference-libril/ril_service.h
@@ -821,6 +821,15 @@
 int getSlicingConfigResponse(int slotId, int responseType, int serial,
                              RIL_Errno e, void *response, size_t responseLen);
 
+int getSimPhonebookRecordsResponse(int slotId, int responseType, int serial,
+                             RIL_Errno e, void *response, size_t responseLen);
+
+int getSimPhonebookCapacityResponse(int slotId, int responseType, int serial,
+                             RIL_Errno e, void *response, size_t responseLen);
+
+int updateSimPhonebookRecordsResponse(int slotId, int responseType, int serial,
+                             RIL_Errno e, void *response, size_t responseLen);
+
 
 pthread_rwlock_t * getRadioServiceRwlock(int slotId);
 
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index d285d84..bfb856b 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -704,9 +704,8 @@
     instance.set_device_title(FLAGS_device_title);
 
     if (FLAGS_protected_vm) {
-      instance.set_virtual_disk_paths({
-        const_instance.PerInstancePath("composite.img")
-      });
+      instance.set_virtual_disk_paths(
+          {const_instance.PerInstancePath("os_composite.img")});
     } else {
       std::vector<std::string> virtual_disk_paths = {
         const_instance.PerInstancePath("overlay.img"),
diff --git a/host/commands/run_cvd/launch.cc b/host/commands/run_cvd/launch.cc
index a2432e1..1599fe0 100644
--- a/host/commands/run_cvd/launch.cc
+++ b/host/commands/run_cvd/launch.cc
@@ -155,8 +155,7 @@
   std::vector<SharedFD> ret;
 
   if (number_of_event_pipes > 0) {
-    auto param_builder = command.GetParameterBuilder();
-    param_builder << "-subscriber_fds=";
+    command.AddParameter("-subscriber_fds=");
     for (unsigned int i = 0; i < number_of_event_pipes; ++i) {
       SharedFD event_pipe_write_end, event_pipe_read_end;
       if (!SharedFD::Pipe(&event_pipe_read_end, &event_pipe_write_end)) {
@@ -164,12 +163,11 @@
         std::exit(RunnerExitCodes::kPipeIOError);
       }
       if (i > 0) {
-        param_builder << ",";
+        command.AppendToLastParameter(",");
       }
-      param_builder << event_pipe_write_end;
+      command.AppendToLastParameter(event_pipe_write_end);
       ret.push_back(event_pipe_read_end);
     }
-    param_builder.Build();
   }
 
   process_monitor->AddCommand(std::move(command));
@@ -434,8 +432,7 @@
 
   auto instance = config.ForDefaultInstance();
   auto ports = instance.modem_simulator_ports();
-  auto param_builder = cmd.GetParameterBuilder();
-  param_builder << "-server_fds=";
+  cmd.AddParameter("-server_fds=");
   for (int i = 0; i < instance_number; ++i) {
     auto pos = ports.find(',');
     auto temp = (pos != std::string::npos) ? ports.substr(0, pos - 1) : ports;
@@ -449,11 +446,10 @@
       std::exit(RunnerExitCodes::kModemSimulatorServerError);
     }
     if (i > 0) {
-      param_builder << ",";
+      cmd.AppendToLastParameter(",");
     }
-    param_builder << socket;
+    cmd.AppendToLastParameter(socket);
   }
-  param_builder.Build();
 
   process_monitor->AddCommand(std::move(cmd));
 }
diff --git a/host/frontend/webrtc/connection_observer.cpp b/host/frontend/webrtc/connection_observer.cpp
index 3be8fa4..a8613e6 100644
--- a/host/frontend/webrtc/connection_observer.cpp
+++ b/host/frontend/webrtc/connection_observer.cpp
@@ -236,42 +236,62 @@
       LOG(ERROR) << "Received invalid JSON object over control channel: " << errorMessage;
       return;
     }
-    auto result =
-        webrtc_streaming::ValidationResult::ValidateJsonObject(evt, "command",
-                           {{"command", Json::ValueType::stringValue},
-                            {"state", Json::ValueType::stringValue}});
+
+    auto result = webrtc_streaming::ValidationResult::ValidateJsonObject(
+        evt, "command",
+        /*required_fields=*/{{"command", Json::ValueType::stringValue}},
+        /*optional_fields=*/
+        {
+            {"button_state", Json::ValueType::stringValue},
+            {"lid_switch_open", Json::ValueType::booleanValue},
+            {"hinge_angle_value", Json::ValueType::intValue},
+        });
     if (!result.ok()) {
       LOG(ERROR) << result.error();
       return;
     }
     auto command = evt["command"].asString();
-    auto state = evt["state"].asString();
 
-    LOG(VERBOSE) << "Control command: " << command << " (" << state << ")";
+    if (command == "device_state") {
+      if (evt.isMember("lid_switch_open")) {
+        // InputManagerService treats a value of 0 as open and 1 as closed, so
+        // invert the lid_switch_open value that is sent to the input device.
+        OnSwitchEvent(SW_LID, !evt["lid_switch_open"].asBool());
+      }
+      // TODO(b/181157794) Propagate hinge angle sensor data.
+      if (evt.isMember("hinge_angle_value")) {
+        LOG(WARNING) << "Hinge angle sensor is not yet implemented.";
+      }
+      return;
+    }
+
+    auto button_state = evt["button_state"].asString();
+    LOG(VERBOSE) << "Control command: " << command << " (" << button_state
+                 << ")";
     if (command == "power") {
-      OnKeyboardEvent(KEY_POWER, state == "down");
+      OnKeyboardEvent(KEY_POWER, button_state == "down");
     } else if (command == "home") {
-      OnKeyboardEvent(KEY_HOMEPAGE, state == "down");
+      OnKeyboardEvent(KEY_HOMEPAGE, button_state == "down");
     } else if (command == "menu") {
-      OnKeyboardEvent(KEY_MENU, state == "down");
+      OnKeyboardEvent(KEY_MENU, button_state == "down");
     } else if (command == "volumemute") {
-      OnKeyboardEvent(KEY_MUTE, state == "down");
+      OnKeyboardEvent(KEY_MUTE, button_state == "down");
     } else if (command == "volumedown") {
-      OnKeyboardEvent(KEY_VOLUMEDOWN, state == "down");
+      OnKeyboardEvent(KEY_VOLUMEDOWN, button_state == "down");
     } else if (command == "volumeup") {
-      OnKeyboardEvent(KEY_VOLUMEUP, state == "down");
+      OnKeyboardEvent(KEY_VOLUMEUP, button_state == "down");
     } else if (commands_to_custom_action_servers_.find(command) !=
                commands_to_custom_action_servers_.end()) {
       // Simple protocol for commands forwarded to action servers:
       //   - Always 128 bytes
-      //   - Format:   command:state
+      //   - Format:   command:button_state
       //   - Example:  my_button:down
-      std::string action_server_message = command + ":" + state;
+      std::string action_server_message = command + ":" + button_state;
       cuttlefish::WriteAll(commands_to_custom_action_servers_[command],
                            action_server_message.c_str(), 128);
     } else {
-      LOG(WARNING) << "Unsupported control command: " << command << " (" << state << ")";
-      // TODO(b/163081337): Handle custom commands.
+      LOG(WARNING) << "Unsupported control command: " << command << " ("
+                   << button_state << ")";
     }
   }
 
diff --git a/host/frontend/webrtc/lib/streamer.cpp b/host/frontend/webrtc/lib/streamer.cpp
index 31ec7fe..255ee88 100644
--- a/host/frontend/webrtc/lib/streamer.cpp
+++ b/host/frontend/webrtc/lib/streamer.cpp
@@ -55,6 +55,9 @@
 constexpr auto kControlPanelButtonTitle = "title";
 constexpr auto kControlPanelButtonIconName = "icon_name";
 constexpr auto kControlPanelButtonShellCommand = "shell_command";
+constexpr auto kControlPanelButtonDeviceStates = "device_states";
+constexpr auto kControlPanelButtonLidSwitchOpen = "lid_switch_open";
+constexpr auto kControlPanelButtonHingeAngleValue = "hinge_angle_value";
 constexpr auto kCustomControlPanelButtonsField = "custom_control_panel_buttons";
 
 void SendJson(WsConnection* ws_conn, const Json::Value& data) {
@@ -99,6 +102,7 @@
   std::string title;
   std::string icon_name;
   std::optional<std::string> shell_command;
+  std::vector<DeviceState> device_states;
 };
 
 // TODO (jemoreira): move to a place in common with the signaling server
@@ -231,12 +235,30 @@
   impl_->hardware_.emplace(key, value);
 }
 
-void Streamer::AddCustomControlPanelButton(
+void Streamer::AddCustomControlPanelButton(const std::string& command,
+                                           const std::string& title,
+                                           const std::string& icon_name) {
+  ControlPanelButtonDescriptor button = {
+      .command = command, .title = title, .icon_name = icon_name};
+  impl_->custom_control_panel_buttons_.push_back(button);
+}
+
+void Streamer::AddCustomControlPanelButtonWithShellCommand(
+    const std::string& command, const std::string& title,
+    const std::string& icon_name, const std::string& shell_command) {
+  ControlPanelButtonDescriptor button = {
+      .command = command, .title = title, .icon_name = icon_name};
+  button.shell_command = shell_command;
+  impl_->custom_control_panel_buttons_.push_back(button);
+}
+
+void Streamer::AddCustomControlPanelButtonWithDeviceStates(
     const std::string& command, const std::string& title,
     const std::string& icon_name,
-    const std::optional<std::string>& shell_command) {
-  ControlPanelButtonDescriptor button = {command, title, icon_name,
-                                         shell_command};
+    const std::vector<DeviceState>& device_states) {
+  ControlPanelButtonDescriptor button = {
+      .command = command, .title = title, .icon_name = icon_name};
+  button.device_states = device_states;
   impl_->custom_control_panel_buttons_.push_back(button);
 }
 
@@ -327,6 +349,21 @@
       button_entry[kControlPanelButtonIconName] = button.icon_name;
       if (button.shell_command) {
         button_entry[kControlPanelButtonShellCommand] = *(button.shell_command);
+      } else if (!button.device_states.empty()) {
+        Json::Value device_states(Json::arrayValue);
+        for (const DeviceState& device_state : button.device_states) {
+          Json::Value device_state_entry;
+          if (device_state.lid_switch_open) {
+            device_state_entry[kControlPanelButtonLidSwitchOpen] =
+                *device_state.lid_switch_open;
+          }
+          if (device_state.hinge_angle_value) {
+            device_state_entry[kControlPanelButtonHingeAngleValue] =
+                *device_state.hinge_angle_value;
+          }
+          device_states.append(device_state_entry);
+        }
+        button_entry[kControlPanelButtonDeviceStates] = device_states;
       }
       custom_control_panel_buttons.append(button_entry);
     }
diff --git a/host/frontend/webrtc/lib/streamer.h b/host/frontend/webrtc/lib/streamer.h
index c9ff382..115d5cb 100644
--- a/host/frontend/webrtc/lib/streamer.h
+++ b/host/frontend/webrtc/lib/streamer.h
@@ -24,6 +24,8 @@
 #include <utility>
 #include <vector>
 
+#include "host/libs/config/custom_actions.h"
+
 #include "host/frontend/webrtc/lib/audio_sink.h"
 #include "host/frontend/webrtc/lib/connection_observer.h"
 #include "host/frontend/webrtc/lib/local_recorder.h"
@@ -91,12 +93,16 @@
   std::shared_ptr<AudioSink> AddAudioStream(const std::string& label);
 
   // Add a custom button to the control panel.
-  //   If this button should be handled by an action server, use nullopt (the
-  //   default) for shell_command.
-  void AddCustomControlPanelButton(
+  void AddCustomControlPanelButton(const std::string& command,
+                                   const std::string& title,
+                                   const std::string& icon_name);
+  void AddCustomControlPanelButtonWithShellCommand(
+      const std::string& command, const std::string& title,
+      const std::string& icon_name, const std::string& shell_command);
+  void AddCustomControlPanelButtonWithDeviceStates(
       const std::string& command, const std::string& title,
       const std::string& icon_name,
-      const std::optional<std::string>& shell_command = std::nullopt);
+      const std::vector<DeviceState>& device_states);
 
   // Register with the operator.
   void Register(std::weak_ptr<OperatorObserver> operator_observer);
diff --git a/host/frontend/webrtc/lib/utils.cpp b/host/frontend/webrtc/lib/utils.cpp
index 117492d..78460c3 100644
--- a/host/frontend/webrtc/lib/utils.cpp
+++ b/host/frontend/webrtc/lib/utils.cpp
@@ -23,23 +23,47 @@
 namespace cuttlefish {
 namespace webrtc_streaming {
 
+namespace {
+
+std::string ValidateField(const Json::Value &obj, const std::string &type,
+                          const std::string &field_name,
+                          const Json::ValueType &field_type, bool required) {
+  if (!obj.isMember(field_name) && !required) {
+    return "";
+  }
+  if (!(obj.isMember(field_name) &&
+        obj[field_name].isConvertibleTo(field_type))) {
+    std::string error_msg = "Expected a field named '";
+    error_msg += field_name + "' of type '";
+    error_msg += std::to_string(field_type);
+    error_msg += "'";
+    if (!type.empty()) {
+      error_msg += " in message of type '" + type + "'";
+    }
+    error_msg += ".";
+    return error_msg;
+  }
+  return "";
+}
+
+}  // namespace
+
 ValidationResult ValidationResult::ValidateJsonObject(
     const Json::Value &obj, const std::string &type,
-    const std::map<std::string, Json::ValueType> &fields) {
-  for (const auto &field_spec : fields) {
-    const auto &field_name = field_spec.first;
-    auto field_type = field_spec.second;
-    if (!(obj.isMember(field_name) &&
-          obj[field_name].isConvertibleTo(field_type))) {
-      std::string error_msg = "Expected a field named '";
-      error_msg += field_name + "' of type '";
-      error_msg += std::to_string(field_type);
-      error_msg += "'";
-      if (!type.empty()) {
-        error_msg += " in message of type '" + type + "'";
-      }
-      error_msg += ".";
-      return {error_msg};
+    const std::map<std::string, Json::ValueType> &required_fields,
+    const std::map<std::string, Json::ValueType> &optional_fields) {
+  for (const auto &field_spec : required_fields) {
+    auto result =
+        ValidateField(obj, type, field_spec.first, field_spec.second, true);
+    if (!result.empty()) {
+      return {result};
+    }
+  }
+  for (const auto &field_spec : optional_fields) {
+    auto result =
+        ValidateField(obj, type, field_spec.first, field_spec.second, false);
+    if (!result.empty()) {
+      return {result};
     }
   }
   return {};
diff --git a/host/frontend/webrtc/lib/utils.h b/host/frontend/webrtc/lib/utils.h
index 1208551..169221c 100644
--- a/host/frontend/webrtc/lib/utils.h
+++ b/host/frontend/webrtc/lib/utils.h
@@ -32,8 +32,9 @@
   // Helper method to ensure a json object has the required fields convertible
   // to the appropriate types.
   static ValidationResult ValidateJsonObject(
-    const Json::Value &obj, const std::string &type,
-    const std::map<std::string, Json::ValueType> &fields);
+      const Json::Value &obj, const std::string &type,
+      const std::map<std::string, Json::ValueType> &required_fields,
+      const std::map<std::string, Json::ValueType> &optional_fields = {});
 
   bool ok() const { return !error_.has_value(); }
   std::string error() const { return error_.value_or(""); }
diff --git a/host/frontend/webrtc/main.cpp b/host/frontend/webrtc/main.cpp
index d6021a2..297c8a6 100644
--- a/host/frontend/webrtc/main.cpp
+++ b/host/frontend/webrtc/main.cpp
@@ -271,11 +271,10 @@
                    << *(custom_action.shell_command);
       }
       const auto button = custom_action.buttons[0];
-      streamer->AddCustomControlPanelButton(button.command, button.title,
-                                            button.icon_name,
-                                            custom_action.shell_command);
-    }
-    if (custom_action.server) {
+      streamer->AddCustomControlPanelButtonWithShellCommand(
+          button.command, button.title, button.icon_name,
+          *(custom_action.shell_command));
+    } else if (custom_action.server) {
       if (action_server_fds.find(*(custom_action.server)) !=
           action_server_fds.end()) {
         LOG(INFO) << "Connecting to custom action server "
@@ -302,6 +301,15 @@
         LOG(ERROR) << "Custom action server not provided as command line flag: "
                    << *(custom_action.server);
       }
+    } else if (!custom_action.device_states.empty()) {
+      if (custom_action.buttons.size() != 1) {
+        LOG(FATAL)
+            << "Expected exactly one button for custom action device states.";
+      }
+      const auto button = custom_action.buttons[0];
+      streamer->AddCustomControlPanelButtonWithDeviceStates(
+          button.command, button.title, button.icon_name,
+          custom_action.device_states);
     }
   }
 
diff --git a/host/frontend/webrtc_operator/assets/js/app.js b/host/frontend/webrtc_operator/assets/js/app.js
index 5ca5fb7..903ed5a 100644
--- a/host/frontend/webrtc_operator/assets/js/app.js
+++ b/host/frontend/webrtc_operator/assets/js/app.js
@@ -251,6 +251,11 @@
                 e => onCustomShellButton(button.shell_command, e),
                 'control_panel_custom_buttons');
             buttons[button.command].adb = true;
+          } else if (button.device_states) {
+            // This button corresponds to variable hardware device state(s).
+            createControlPanelButton(button.command, button.title, button.icon_name,
+                getCustomDeviceStateButtonCb(button.device_states),
+                'control_panel_custom_buttons');
           } else {
             // This button's command is handled by custom action server.
             createControlPanelButton(button.command, button.title, button.icon_name,
@@ -267,11 +272,11 @@
       // connected long after the device boots up.
       deviceConnection.sendControlMessage(JSON.stringify({
         command: 'home',
-        state: 'down',
+        button_state: 'down',
       }));
       deviceConnection.sendControlMessage(JSON.stringify({
         command: 'home',
-        state: 'up',
+        button_state: 'up',
       }));
       // Show the error message and disable buttons when the WebRTC connection fails.
       deviceConnection.onConnectionStateChange(state => {
@@ -329,7 +334,7 @@
     }
     deviceConnection.sendControlMessage(JSON.stringify({
       command: e.target.dataset.command,
-      state: e.type == 'mousedown' ? "down" : "up",
+      button_state: e.type == 'mousedown' ? "down" : "up",
     }));
   }
 
@@ -343,6 +348,7 @@
           (currentRotation == 0 ? 'landscape' : 'portrait'))
     }
   }
+
   function onCustomShellButton(shell_command, e) {
     // Attempt to init adb again, in case the initial connection failed.
     // This succeeds immediately if already connected.
@@ -352,6 +358,26 @@
     }
   }
 
+  function getCustomDeviceStateButtonCb(device_states) {
+    let states = device_states;
+    let index = 0;
+    return e => {
+      if (e.type == 'mousedown') {
+        // Reset any overridden device state.
+        adbShell('cmd device_state state reset');
+        // Send a device_state message for the current state.
+        let message = {
+          command: 'device_state',
+          ...states[index],
+        };
+        deviceConnection.sendControlMessage(JSON.stringify(message));
+        console.log(JSON.stringify(message));
+        // Cycle to the next state.
+        index = (index + 1) % states.length;
+      }
+    }
+  }
+
   function startMouseTracking() {
     if (window.PointerEvent) {
       deviceScreen.addEventListener('pointerdown', onStartDrag);
diff --git a/host/libs/config/custom_actions.cpp b/host/libs/config/custom_actions.cpp
index 2c78eba..1cc2e06 100644
--- a/host/libs/config/custom_actions.cpp
+++ b/host/libs/config/custom_actions.cpp
@@ -29,6 +29,9 @@
 
 const char* kCustomActionShellCommand = "shell_command";
 const char* kCustomActionServer = "server";
+const char* kCustomActionDeviceStates = "device_states";
+const char* kCustomActionDeviceStateLidSwitchOpen = "lid_switch_open";
+const char* kCustomActionDeviceStateHingeAngleValue = "hinge_angle_value";
 const char* kCustomActionButton = "button";
 const char* kCustomActionButtons = "buttons";
 const char* kCustomActionButtonCommand = "command";
@@ -39,11 +42,15 @@
 
 
 CustomActionConfig::CustomActionConfig(const Json::Value& dictionary) {
+  if (dictionary.isMember(kCustomActionShellCommand) +
+          dictionary.isMember(kCustomActionServer) +
+          dictionary.isMember(kCustomActionDeviceStates) !=
+      1) {
+    LOG(FATAL) << "Custom action must contain exactly one of shell_command, "
+               << "server, or device_states";
+    return;
+  }
   if (dictionary.isMember(kCustomActionShellCommand)) {
-    if (dictionary.isMember(kCustomActionServer)) {
-      LOG(ERROR) << "Custom action contains both shell command and action server.";
-      return;
-    }
     // Shell command with one button.
     Json::Value button_entry = dictionary[kCustomActionButton];
     buttons = {{button_entry[kCustomActionButtonCommand].asString(),
@@ -60,8 +67,29 @@
       buttons.push_back(button);
     }
     server = dictionary[kCustomActionServer].asString();
+  } else if (dictionary.isMember(kCustomActionDeviceStates)) {
+    // Device state(s) with one button.
+    // Each button press cycles to the next state, then repeats to the first.
+    Json::Value button_entry = dictionary[kCustomActionButton];
+    buttons = {{button_entry[kCustomActionButtonCommand].asString(),
+                button_entry[kCustomActionButtonTitle].asString(),
+                button_entry[kCustomActionButtonIconName].asString()}};
+    for (const Json::Value& device_state_entry :
+         dictionary[kCustomActionDeviceStates]) {
+      DeviceState state;
+      if (device_state_entry.isMember(kCustomActionDeviceStateLidSwitchOpen)) {
+        state.lid_switch_open =
+            device_state_entry[kCustomActionDeviceStateLidSwitchOpen].asBool();
+      }
+      if (device_state_entry.isMember(
+              kCustomActionDeviceStateHingeAngleValue)) {
+        state.hinge_angle_value =
+            device_state_entry[kCustomActionDeviceStateHingeAngleValue].asInt();
+      }
+      device_states.push_back(state);
+    }
   } else {
-    LOG(ERROR) << "Unknown custom action format.";
+    LOG(FATAL) << "Unknown custom action type.";
   }
 }
 
@@ -88,8 +116,30 @@
       button_entry[kCustomActionButtonIconName] = button.icon_name;
       custom_action[kCustomActionButtons].append(button_entry);
     }
+  } else if (!device_states.empty()) {
+    // Device state(s) with one button.
+    custom_action[kCustomActionDeviceStates] = Json::Value(Json::arrayValue);
+    for (const auto& device_state : device_states) {
+      Json::Value device_state_entry;
+      if (device_state.lid_switch_open) {
+        device_state_entry[kCustomActionDeviceStateLidSwitchOpen] =
+            *device_state.lid_switch_open;
+      }
+      if (device_state.hinge_angle_value) {
+        device_state_entry[kCustomActionDeviceStateHingeAngleValue] =
+            *device_state.hinge_angle_value;
+      }
+      custom_action[kCustomActionDeviceStates].append(device_state_entry);
+    }
+    custom_action[kCustomActionButton] = Json::Value();
+    custom_action[kCustomActionButton][kCustomActionButtonCommand] =
+        buttons[0].command;
+    custom_action[kCustomActionButton][kCustomActionButtonTitle] =
+        buttons[0].title;
+    custom_action[kCustomActionButton][kCustomActionButtonIconName] =
+        buttons[0].icon_name;
   } else {
-    LOG(ERROR) << "Unknown custom action type.";
+    LOG(FATAL) << "Unknown custom action type.";
   }
   return custom_action;
 }
diff --git a/host/libs/config/custom_actions.h b/host/libs/config/custom_actions.h
index 51e73ba..6279401 100644
--- a/host/libs/config/custom_actions.h
+++ b/host/libs/config/custom_actions.h
@@ -29,6 +29,11 @@
   std::string icon_name;
 };
 
+struct DeviceState {
+  std::optional<bool> lid_switch_open;
+  std::optional<int> hinge_angle_value;
+};
+
 struct CustomActionConfig {
   CustomActionConfig(const Json::Value&);
   Json::Value ToJson() const;
@@ -36,6 +41,7 @@
   std::vector<ControlPanelButton> buttons;
   std::optional<std::string> shell_command;
   std::optional<std::string> server;
+  std::vector<DeviceState> device_states;
 };
 
 }  // namespace cuttlefish
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index 89cc84d..e2ec717 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -110,7 +110,7 @@
         "androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_1),
         "androidboot.hardware.gralloc=minigbm",
         "androidboot.hardware.hwcomposer=ranchu",
-        "androidboot.hardware.egl=swiftshader",
+        "androidboot.hardware.egl=angle",
         "androidboot.hardware.vulkan=pastel",
     };
   }
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index 9eea793..ed61c69 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -100,11 +100,11 @@
     // with properities lead to non-deterministic behavior while loading the
     // HALs.
     return {
-      "androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_1),
-      "androidboot.hardware.gralloc=minigbm",
-      "androidboot.hardware.hwcomposer=cutf",
-      "androidboot.hardware.egl=swiftshader",
-      "androidboot.hardware.vulkan=pastel",
+        "androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_1),
+        "androidboot.hardware.gralloc=minigbm",
+        "androidboot.hardware.hwcomposer=ranchu",
+        "androidboot.hardware.egl=swiftshader",
+        "androidboot.hardware.vulkan=pastel",
     };
   }
 
diff --git a/shared/config/config_foldable.json b/shared/config/config_foldable.json
index f8cee39..7a1e731 100644
--- a/shared/config/config_foldable.json
+++ b/shared/config/config_foldable.json
@@ -4,13 +4,29 @@
 	"dpi" : 386,
 	"memory_mb" : 4096,
 	"custom_actions" : [
-		{
-			"shell_command":"dumpsys device_state | grep mCommittedState | grep OPENED && cmd device_state state 0 || cmd device_state state 2",
-			"button":{
-				"command":"fold",
-				"title":"Fold/Unfold",
-				"icon_name":"chrome_reader_mode"
-			}
-		}
+                {
+                        "device_states": [
+                                {
+                                        "lid_switch_open": false
+                                }
+                        ],
+                        "button":{
+                                "command":"device_state_closed",
+                                "title":"Device State Closed",
+                                "icon_name":"smartphone"
+                        }
+                },
+                {
+                        "device_states": [
+                                {
+                                        "lid_switch_open": true
+                                }
+                        ],
+                        "button":{
+                                "command":"device_state_opened",
+                                "title":"Device State Opened",
+                                "icon_name":"tablet"
+                        }
+                }
 	]
 }
diff --git a/shared/config/config_phone.json b/shared/config/config_phone.json
index 3a0ee64..69ad977 100644
--- a/shared/config/config_phone.json
+++ b/shared/config/config_phone.json
@@ -1,6 +1,6 @@
 {
 	"x_res" : 720,
 	"y_res" : 1280,
-	"dpi" : 240,
+	"dpi" : 320,
 	"memory_mb" : 2048
 }
diff --git a/shared/config/config_tablet.json b/shared/config/config_tablet.json
index 87bc145..832d637 100644
--- a/shared/config/config_tablet.json
+++ b/shared/config/config_tablet.json
@@ -1,6 +1,6 @@
 {
 	"x_res" : 2560,
 	"y_res" : 1800,
-	"dpi" : 240,
+	"dpi" : 320,
 	"memory_mb" : 4096
 }
diff --git a/shared/device.mk b/shared/device.mk
index 212e372..7f97e5b 100644
--- a/shared/device.mk
+++ b/shared/device.mk
@@ -465,7 +465,7 @@
 # Gatekeeper
 #
 ifeq ($(LOCAL_GATEKEEPER_PRODUCT_PACKAGE),)
-       LOCAL_GATEKEEPER_PRODUCT_PACKAGE := [email protected]
+       LOCAL_GATEKEEPER_PRODUCT_PACKAGE := [email protected]
 endif
 PRODUCT_PACKAGES += \
     $(LOCAL_GATEKEEPER_PRODUCT_PACKAGE)
@@ -520,7 +520,7 @@
 # Keymaster HAL
 #
 ifeq ($(LOCAL_KEYMASTER_PRODUCT_PACKAGE),)
-       LOCAL_KEYMASTER_PRODUCT_PACKAGE := [email protected]
+       LOCAL_KEYMASTER_PRODUCT_PACKAGE := [email protected]
 endif
 PRODUCT_PACKAGES += \
     $(LOCAL_KEYMASTER_PRODUCT_PACKAGE)
@@ -532,7 +532,7 @@
        LOCAL_KEYMINT_PRODUCT_PACKAGE := android.hardware.security.keymint-service
 endif
 # PRODUCT_PACKAGES += \
-    $(LOCAL_KEYMINT_PRODUCT_PACKAGE)
+#    $(LOCAL_KEYMINT_PRODUCT_PACKAGE)
 
 #
 # Power HAL
@@ -559,7 +559,8 @@
     android.hardware.neuralnetworks-service-sample-float-fast \
     android.hardware.neuralnetworks-service-sample-float-slow \
     android.hardware.neuralnetworks-service-sample-minimal \
-    android.hardware.neuralnetworks-service-sample-quant
+    android.hardware.neuralnetworks-service-sample-quant \
+    android.hardware.neuralnetworks-shim-service-sample
 
 #
 # USB
diff --git a/shared/foldable/device_state_configuration.xml b/shared/foldable/device_state_configuration.xml
index d2c7bf3..b32eced 100644
--- a/shared/foldable/device_state_configuration.xml
+++ b/shared/foldable/device_state_configuration.xml
@@ -8,7 +8,15 @@
       </lid-switch>
     </conditions>
   </device-state>
-  <!-- TODO(b/181583265): Add state 1 for HALF_OPENED using hinge sensor. -->
+  <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>
+    <name>HALF_OPENED</name>
+    <conditions></conditions>
+  </device-state>
   <device-state>
     <identifier>2</identifier>
     <name>OPENED</name>
diff --git a/shared/foldable/overlay/frameworks/base/core/res/res/values/config.xml b/shared/foldable/overlay/frameworks/base/core/res/res/values/config.xml
index bbcd179..2c540e1 100644
--- a/shared/foldable/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/shared/foldable/overlay/frameworks/base/core/res/res/values/config.xml
@@ -20,6 +20,14 @@
   <!-- Indicate the display area rect for foldable devices in folded state. -->
   <!-- left and right bounds come from:  (open_width/2) +/- (folded_width)/2 -->
   <string name="config_foldedArea">476 0 1292 2208</string>
+  <!-- WindowsManager JetPack display features -->
+  <string name="config_display_features" translatable="false">fold-[884,0,884,2208]</string>
+  <!-- Map of System DeviceState supplied by DeviceStateManager to WM Jetpack posture. -->
+  <string-array name="config_device_state_postures" translatable="false">
+      <item>0:1</item> <!-- CLOSED : STATE_FLAT -->
+      <item>3:2</item> <!-- HALF_OPENED : STATE_HALF_OPENED -->
+      <item>2:3</item> <!-- OPENED : STATE_FLIPPED -->
+  </string-array>
   <!-- The device states (supplied by DeviceStateManager) that should be treated as folded by the
        display fold controller. -->
   <integer-array name="config_foldedDeviceStates" translatable="false">
diff --git a/shared/sepolicy/vendor/file_contexts b/shared/sepolicy/vendor/file_contexts
index 8259407..d64fb17 100644
--- a/shared/sepolicy/vendor/file_contexts
+++ b/shared/sepolicy/vendor/file_contexts
@@ -76,6 +76,7 @@
 /vendor/bin/hw/android\.hardware\.health\.storage-service\.cuttlefish u:object_r:hal_health_storage_default_exec:s0
 /vendor/bin/hw/android\.hardware\.lights-service\.example u:object_r:hal_light_default_exec:s0
 /vendor/bin/hw/android\.hardware\.neuralnetworks@1\.3-service-sample-.*   u:object_r:hal_neuralnetworks_sample_exec:s0
+/vendor/bin/hw/android\.hardware\.neuralnetworks-shim-service-sample   u:object_r:hal_neuralnetworks_sample_exec:s0
 /vendor/bin/hw/android\.hardware\.neuralnetworks-service-sample-.*   u:object_r:hal_neuralnetworks_sample_exec:s0
 /vendor/bin/hw/android\.hardware\.vibrator@1\.x-service\.example u:object_r:hal_vibrator_default_exec:s0
 /vendor/bin/setup_wifi  u:object_r:setup_wifi_exec:s0
diff --git a/shared/sepolicy/vendor/service_contexts b/shared/sepolicy/vendor/service_contexts
index 7b8a515..d20d026 100644
--- a/shared/sepolicy/vendor/service_contexts
+++ b/shared/sepolicy/vendor/service_contexts
@@ -3,6 +3,7 @@
 android.hardware.neuralnetworks.IDevice/nnapi-sample_float_slow u:object_r:hal_neuralnetworks_service:s0
 android.hardware.neuralnetworks.IDevice/nnapi-sample_minimal    u:object_r:hal_neuralnetworks_service:s0
 android.hardware.neuralnetworks.IDevice/nnapi-sample_quant    u:object_r:hal_neuralnetworks_service:s0
+android.hardware.neuralnetworks.IDevice/nnapi-sample_sl_shim  u:object_r:hal_neuralnetworks_service:s0
 
 # Binder service mappings
 gce                                       u:object_r:gce_service:s0
diff --git a/shared/tv/device.mk b/shared/tv/device.mk
index 4ab5928..2faccff 100644
--- a/shared/tv/device.mk
+++ b/shared/tv/device.mk
@@ -32,6 +32,9 @@
 # HDMI CEC HAL
 PRODUCT_PACKAGES += [email protected]
 
+# Setup HDMI CEC as Playback Device
+PRODUCT_PROPERTY_OVERRIDES += ro.hdmi.device_type=4
+
 # Tuner HAL
 PRODUCT_PACKAGES += [email protected]
 
diff --git a/tests/hal/hal_implementation_test.cpp b/tests/hal/hal_implementation_test.cpp
index 74bbd68..c389f53 100644
--- a/tests/hal/hal_implementation_test.cpp
+++ b/tests/hal/hal_implementation_test.cpp
@@ -102,11 +102,6 @@
     "android.hardware.common.fmq.",
     "android.hardware.graphics.common.",
 
-    // Temporarily add the keystore2 interface. The service implementation is
-    // in full swing but we cannot register the service by default just yet.
-    // b/170144267
-    "android.system.keystore2.",
-
     // These KeyMaster types are in an AIDL types-only HAL because they're used
     // by the Identity Credential AIDL HAL. Remove this when fully porting
     // KeyMaster to AIDL.
diff --git a/tools/upload_via_ssh.py b/tools/upload_via_ssh.py
index a31aaf9..5359473 100755
--- a/tools/upload_via_ssh.py
+++ b/tools/upload_via_ssh.py
@@ -12,7 +12,7 @@
   dir = os.getcwd()
   try:
     os.chdir(args.image_dir)
-    images = glob.glob('*.img')
+    images = glob.glob('*.img') + ["bootloader"]
     if len(images) == 0:
       raise OSError('File not found: ' + args.image_dir + '/*.img')
     subprocess.check_call(