Merge "Bump Cuttlefish SHIPPING_API_LEVEL" into main
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index 79350c2..7baae38 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -710,15 +710,15 @@
     guest_config.mouse_supported =
         res_mouse_support.ok() && res_mouse_support.value() == "supported";
 
-    auto res_custom_keyboard_config = GetAndroidInfoConfig(
-        instance_android_info_txt, "custom_keyboard_config");
+    auto res_custom_keyboard_config =
+        GetAndroidInfoConfig(instance_android_info_txt, "custom_keyboard");
     if (res_custom_keyboard_config.ok()) {
       guest_config.custom_keyboard_config =
           DefaultHostArtifactsPath(res_custom_keyboard_config.value());
     }
 
-    auto res_domkey_mapping_config = GetAndroidInfoConfig(
-        instance_android_info_txt, "domkey_mapping_config");
+    auto res_domkey_mapping_config =
+        GetAndroidInfoConfig(instance_android_info_txt, "domkey_mapping");
     if (res_domkey_mapping_config.ok()) {
       guest_config.domkey_mapping_config =
           DefaultHostArtifactsPath(res_domkey_mapping_config.value());
@@ -1985,7 +1985,7 @@
 }
 
 Result<void> SetDefaultFlagsForQemu(
-    Arch target_arch,
+    const std::vector<GuestConfig>& guest_configs,
     std::map<std::string, std::string>& name_to_default_value) {
   auto instance_nums =
       CF_EXPECT(InstanceNumsCalculator().FromGlobalGflags().Calculate());
@@ -1996,25 +1996,51 @@
       CF_EXPECT(GET_FLAG_BOOL_VALUE(start_webrtc));
   std::vector<std::string> system_image_dir =
       CF_EXPECT(GET_FLAG_STR_VALUE(system_image_dir));
+  std::string curr_bootloader = "";
   std::string curr_android_efi_loader = "";
+  std::string default_bootloader = "";
   std::string default_android_efi_loader = "";
   std::string default_start_webrtc = "";
 
   for (int instance_index = 0; instance_index < instance_nums.size();
        instance_index++) {
     if (instance_index >= system_image_dir.size()) {
+      curr_bootloader = system_image_dir[0];
       curr_android_efi_loader = system_image_dir[0];
     } else {
+      curr_bootloader = system_image_dir[instance_index];
       curr_android_efi_loader = system_image_dir[instance_index];
     }
+    curr_bootloader += "/bootloader";
     curr_android_efi_loader += "/android_efi_loader.efi";
 
+    // /bootloader isn't presented in the output folder by default and can be
+    // only fetched by --bootloader in fetch_cvd, so pick it only in case
+    // it's presented.
+    if (!FileExists(curr_bootloader)) {
+      // Fallback to default bootloader
+      curr_bootloader = DefaultHostArtifactsPath("etc/bootloader_");
+      if (guest_configs[instance_index].target_arch == Arch::Arm64) {
+        curr_bootloader += "aarch64";
+      } else {
+        curr_bootloader += "x86_64";
+      }
+      curr_bootloader += "/bootloader.qemu";
+    }
+
     if (instance_index > 0) {
+      default_bootloader += ",";
       default_android_efi_loader += ",";
       default_start_webrtc += ",";
     }
 
-    default_android_efi_loader += curr_android_efi_loader;
+    default_bootloader += curr_bootloader;
+    // EFI loader isn't presented in the output folder by default and can be
+    // only fetched by --uefi_app_build in fetch_cvd, so pick it only in case
+    // it's presented.
+    if (FileExists(curr_android_efi_loader)) {
+      default_android_efi_loader += curr_android_efi_loader;
+    }
     if (gpu_mode_vec[instance_index] == kGpuModeGuestSwiftshader &&
         !start_webrtc_vec[instance_index]) {
       // This makes WebRTC the default streamer unless the user requests
@@ -2031,29 +2057,11 @@
   SetCommandLineOptionWithMode("start_webrtc", default_start_webrtc.c_str(),
                                SET_FLAGS_DEFAULT);
 
-  std::string default_bootloader = DefaultHostArtifactsPath("etc/bootloader_");
-  if (target_arch == Arch::Arm) {
-    // Bootloader is unstable >512MB RAM on 32-bit ARM
-    SetCommandLineOptionWithMode("memory_mb", "512", SET_FLAGS_VALUE);
-    default_bootloader += "arm";
-  } else if (target_arch == Arch::Arm64) {
-    default_bootloader += "aarch64";
-  } else if (target_arch == Arch::RiscV64) {
-    default_bootloader += "riscv64";
-  } else {
-    default_bootloader += "x86_64";
-  }
-  default_bootloader += "/bootloader.qemu";
   SetCommandLineOptionWithMode("bootloader", default_bootloader.c_str(),
                                SET_FLAGS_DEFAULT);
-  // EFI loader isn't presented in the output folder by default and can be only
-  // fetched by --uefi_app_build in fetch_cvd, so pick it only in case it's
-  // presented.
-  if (FileExists(default_android_efi_loader)) {
-    SetCommandLineOptionWithMode("android_efi_loader",
-                                 default_android_efi_loader.c_str(),
-                                 SET_FLAGS_DEFAULT);
-  }
+  SetCommandLineOptionWithMode("android_efi_loader",
+                               default_android_efi_loader.c_str(),
+                               SET_FLAGS_DEFAULT);
   return {};
 }
 
@@ -2076,43 +2084,50 @@
   std::vector<std::string> system_image_dir =
       CF_EXPECT(GET_FLAG_STR_VALUE(system_image_dir));
   std::string curr_android_efi_loader = "";
-  std::string cur_bootloader = "";
+  std::string curr_bootloader = "";
   std::string default_android_efi_loader = "";
   std::string default_bootloader = "";
   std::string default_enable_sandbox_str = "";
   for (int instance_index = 0; instance_index < instance_nums.size();
        instance_index++) {
-    if (guest_configs[instance_index].android_version_number == "11.0.0") {
-      cur_bootloader = DefaultHostArtifactsPath("etc/bootloader_");
-      if (guest_configs[instance_index].target_arch == Arch::Arm64) {
-        cur_bootloader += "aarch64";
-      } else {
-        cur_bootloader += "x86_64";
-      }
-      cur_bootloader += "/bootloader.crosvm";
-    } else {
-      if (instance_index >= system_image_dir.size()) {
-        cur_bootloader = system_image_dir[0];
-      } else {
-        cur_bootloader = system_image_dir[instance_index];
-      }
-      cur_bootloader += "/bootloader";
-    }
     if (instance_index >= system_image_dir.size()) {
+      curr_bootloader = system_image_dir[0];
       curr_android_efi_loader = system_image_dir[0];
     } else {
+      curr_bootloader = system_image_dir[instance_index];
       curr_android_efi_loader = system_image_dir[instance_index];
     }
+    curr_bootloader += "/bootloader";
     curr_android_efi_loader += "/android_efi_loader.efi";
 
+    // /bootloader isn't presented in the output folder by default and can be
+    // only fetched by --bootloader in fetch_cvd, so pick it only in case
+    // it's presented.
+    if (!FileExists(curr_bootloader)) {
+      // Fallback to default bootloader
+      curr_bootloader = DefaultHostArtifactsPath("etc/bootloader_");
+      if (guest_configs[instance_index].target_arch == Arch::Arm64) {
+        curr_bootloader += "aarch64";
+      } else {
+        curr_bootloader += "x86_64";
+      }
+      curr_bootloader += "/bootloader.crosvm";
+    }
+
     if (instance_index > 0) {
       default_bootloader += ",";
       default_android_efi_loader += ",";
       default_enable_sandbox_str += ",";
       default_start_webrtc += ",";
     }
-    default_bootloader += cur_bootloader;
-    default_android_efi_loader += curr_android_efi_loader;
+
+    default_bootloader += curr_bootloader;
+    // EFI loader isn't presented in the output folder by default and can be
+    // only fetched by --uefi_app_build in fetch_cvd, so pick it only in case
+    // it's presented.
+    if (FileExists(curr_android_efi_loader)) {
+      default_android_efi_loader += curr_android_efi_loader;
+    }
     default_enable_sandbox_str += fmt::format("{}", default_enable_sandbox);
     if (!start_webrtc_vec[instance_index]) {
       // This makes WebRTC the default streamer unless the user requests
@@ -2126,14 +2141,9 @@
   }
   SetCommandLineOptionWithMode("bootloader", default_bootloader.c_str(),
                                SET_FLAGS_DEFAULT);
-  // EFI loader isn't presented in the output folder by default and can be only
-  // fetched by --uefi_app_build in fetch_cvd, so pick it only in case it's
-  // presented.
-  if (FileExists(default_android_efi_loader)) {
-    SetCommandLineOptionWithMode("android_efi_loader",
-                                 default_android_efi_loader.c_str(),
-                                 SET_FLAGS_DEFAULT);
-  }
+  SetCommandLineOptionWithMode("android_efi_loader",
+                               default_android_efi_loader.c_str(),
+                               SET_FLAGS_DEFAULT);
   // This is the 1st place to set "start_webrtc" flag value
   SetCommandLineOptionWithMode("start_webrtc", default_start_webrtc.c_str(),
                                SET_FLAGS_DEFAULT);
@@ -2224,7 +2234,7 @@
   auto name_to_default_value = CurrentFlagsToDefaultValue();
 
   if (vmm == VmmMode::kQemu) {
-    CF_EXPECT(SetDefaultFlagsForQemu(guest_configs[0].target_arch, name_to_default_value));
+    CF_EXPECT(SetDefaultFlagsForQemu(guest_configs, name_to_default_value));
   } else if (vmm == VmmMode::kCrosvm) {
     CF_EXPECT(SetDefaultFlagsForCrosvm(guest_configs, name_to_default_value));
   } else if (vmm == VmmMode::kGem5) {
diff --git a/host/commands/casimir_control_server/Android.bp b/host/commands/casimir_control_server/Android.bp
index afaddb4..2caed5c 100644
--- a/host/commands/casimir_control_server/Android.bp
+++ b/host/commands/casimir_control_server/Android.bp
@@ -68,6 +68,7 @@
     srcs: [
         "casimir_controller.cpp",
         "hex.cpp",
+        "crc.cpp",
         "main.cpp",
     ],
     cflags: [
diff --git a/host/commands/casimir_control_server/casimir_control.proto b/host/commands/casimir_control_server/casimir_control.proto
index a5434e9..4cc3ea4 100644
--- a/host/commands/casimir_control_server/casimir_control.proto
+++ b/host/commands/casimir_control_server/casimir_control.proto
@@ -23,6 +23,7 @@
   rpc PollA (Void) returns (SenderId) {}
   rpc SetRadioState(RadioState) returns (Void) {}
   rpc SetPowerLevel(PowerLevel) returns (Void) {}
+  rpc SendBroadcast (SendBroadcastRequest) returns (SendBroadcastResponse) {}
   rpc Init(Void) returns (Void) {}
   rpc Close(Void) returns (Void) {}
 }
@@ -49,4 +50,26 @@
 
 message PowerLevel {
   uint32 power_level = 1;
-}
\ No newline at end of file
+}
+
+message TransceiveConfiguration {
+    // A, B, F, V
+    optional string type = 1;
+    optional bool crc = 2;
+    // 0 to 8
+    optional uint32 bits = 3;
+    // 106, 212, 424, 848, 53, 26
+    optional uint32 bitrate = 4;
+    // value in microseconds
+    optional uint32 timeout = 5;
+    // 0 to 100
+    optional double power = 6;
+}
+
+message SendBroadcastRequest {
+    string data = 1;
+    optional TransceiveConfiguration configuration = 2;
+}
+
+message SendBroadcastResponse {
+}
diff --git a/host/commands/casimir_control_server/casimir_controller.cpp b/host/commands/casimir_control_server/casimir_controller.cpp
index fdf214f..e90887b 100644
--- a/host/commands/casimir_control_server/casimir_controller.cpp
+++ b/host/commands/casimir_control_server/casimir_controller.cpp
@@ -18,6 +18,9 @@
 #include <chrono>
 #include <cstdint>
 
+#include "host/commands/casimir_control_server/crc.h"
+
+#include "casimir_control.grpc.pb.h"
 #include "casimir_controller.h"
 
 namespace cuttlefish {
@@ -87,7 +90,11 @@
 Result<uint16_t> CasimirController::SelectNfcA() {
   PollCommandBuilder poll_command;
   poll_command.technology_ = Technology::NFC_A;
+  poll_command.format_ = PollingFrameFormat::SHORT;
+  poll_command.bitrate_ = BitRate::BIT_RATE_106_KBIT_S;
   poll_command.power_level_ = power_level;
+  // WUPA
+  poll_command.payload_ = std::vector<uint8_t>{0x52};
   CF_EXPECT(Write(poll_command), "Failed to send NFC-A poll command");
 
   auto res = CF_EXPECT(ReadRfPacket(10s), "Failed to get NFC-A poll response");
@@ -106,6 +113,7 @@
   T4ATSelectCommandBuilder t4at_select_command;
   t4at_select_command.sender_ = sender_id;
   t4at_select_command.param_ = 0;
+  t4at_select_command.bitrate_ = BitRate::BIT_RATE_106_KBIT_S;
   CF_EXPECT(Write(t4at_select_command), "Failed to send T4AT select command");
 
   auto res = CF_EXPECT(ReadRfPacket(1s), "Failed to get T4AT response");
@@ -138,6 +146,7 @@
   data_builder.receiver_ = receiver_id;
   data_builder.technology_ = Technology::NFC_A;
   data_builder.protocol_ = Protocol::ISO_DEP;
+  data_builder.bitrate_ = BitRate::BIT_RATE_106_KBIT_S;
 
   CF_EXPECT(Write(data_builder), "Failed to send APDU bytes");
 
@@ -152,6 +161,83 @@
   return CF_ERR("Invalid APDU response");
 }
 
+Result<std::tuple<std::vector<uint8_t>, std::string, bool, uint32_t, uint32_t,
+                  uint32_t, double>>
+CasimirController::SendBroadcast(std::vector<uint8_t> data, std::string type,
+                                 bool crc, uint8_t bits, uint32_t bitrate,
+                                 uint32_t timeout, double power) {
+  PollCommandBuilder poll_command;
+
+  if (type == "A") {
+    poll_command.technology_ = Technology::NFC_A;
+    if (crc) {
+      data = CF_EXPECT(WithCrc16A(data), "Could not append CRC16A");
+    }
+  } else if (type == "B") {
+    poll_command.technology_ = Technology::NFC_B;
+    if (crc) {
+      data = CF_EXPECT(WithCrc16B(data), "Could not append CRC16B");
+    }
+    if (bits != 8) {
+      return CF_ERR(
+          "Sending NFC-B data with != 8 bits in the last byte is unsupported");
+    }
+  } else if (type == "F") {
+    poll_command.technology_ = Technology::NFC_F;
+    if (!crc) {
+      // For NFC-F, CRC also assumes preamble
+      return CF_ERR("Sending NFC-F data without CRC is unsupported");
+    }
+    if (bits != 8) {
+      return CF_ERR(
+          "Sending NFC-F data with != 8 bits in the last byte is unsupported");
+    }
+  } else if (type == "V") {
+    poll_command.technology_ = Technology::NFC_V;
+  } else {
+    poll_command.technology_ = Technology::RAW;
+  }
+
+  if (bitrate == 106) {
+    poll_command.bitrate_ = BitRate::BIT_RATE_106_KBIT_S;
+  } else if (bitrate == 212) {
+    poll_command.bitrate_ = BitRate::BIT_RATE_212_KBIT_S;
+  } else if (bitrate == 424) {
+    poll_command.bitrate_ = BitRate::BIT_RATE_424_KBIT_S;
+  } else if (bitrate == 848) {
+    poll_command.bitrate_ = BitRate::BIT_RATE_848_KBIT_S;
+  } else if (bitrate == 1695) {
+    poll_command.bitrate_ = BitRate::BIT_RATE_1695_KBIT_S;
+  } else if (bitrate == 3390) {
+    poll_command.bitrate_ = BitRate::BIT_RATE_3390_KBIT_S;
+  } else if (bitrate == 6780) {
+    poll_command.bitrate_ = BitRate::BIT_RATE_6780_KBIT_S;
+  } else if (bitrate == 26) {
+    poll_command.bitrate_ = BitRate::BIT_RATE_26_KBIT_S;
+  } else {
+    return CF_ERR("Proper bitrate was not provided: " << bitrate);
+  }
+
+  poll_command.payload_ = std::move(data);
+
+  if (bits > 8) {
+    return CF_ERR("There can not be more than 8 bits in last byte: " << bits);
+  }
+  poll_command.format_ =
+      bits != 8 ? PollingFrameFormat::SHORT : PollingFrameFormat::LONG;
+
+  // Adjust range of values from 0-100 to 0-12
+  poll_command.power_level_ = static_cast<int>(std::round(power * 12 / 100));
+
+  CF_EXPECT(Write(poll_command), "Failed to send broadcast frame");
+
+  if (timeout != 0) {
+    ReadRfPacket(std::chrono::microseconds(timeout));
+  }
+
+  return std::make_tuple(data, type, crc, bits, bitrate, timeout, power);
+}
+
 Result<void> CasimirController::Write(const RfPacketBuilder& rf_packet) {
   std::vector<uint8_t> raw_bytes = rf_packet.SerializeToBytes();
   uint16_t header_bytes_le = htole16(raw_bytes.size());
@@ -171,17 +257,21 @@
 }
 
 Result<std::shared_ptr<std::vector<uint8_t>>> CasimirController::ReadExact(
-    size_t size, std::chrono::milliseconds timeout) {
+    size_t size, std::chrono::microseconds timeout) {
   size_t total_read = 0;
   auto out = std::make_shared<std::vector<uint8_t>>(size);
   auto prev_time = std::chrono::steady_clock::now();
-  while (timeout.count() > 0) {
+  while (
+      std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count() >
+      0) {
     PollSharedFd poll_fd = {
         .fd = sock_,
         .events = EPOLLIN,
         .revents = 0,
     };
-    int res = sock_.Poll(&poll_fd, 1, timeout.count());
+    int res = sock_.Poll(
+        &poll_fd, 1,
+        std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count());
     CF_EXPECT_GE(res, 0, "Failed to poll on the casimir socket");
     CF_EXPECT_EQ(poll_fd.revents, EPOLLIN,
                  "Unexpected poll result for reading");
@@ -199,7 +289,7 @@
     }
 
     auto current_time = std::chrono::steady_clock::now();
-    timeout -= std::chrono::duration_cast<std::chrono::milliseconds>(
+    timeout -= std::chrono::duration_cast<std::chrono::microseconds>(
         current_time - prev_time);
   }
 
@@ -209,7 +299,7 @@
 // Note: Although rf_packets.h doesn't document nor include packet header,
 // the header is necessary to know total packet size.
 Result<std::shared_ptr<std::vector<uint8_t>>> CasimirController::ReadRfPacket(
-    std::chrono::milliseconds timeout) {
+    std::chrono::microseconds timeout) {
   auto start_time = std::chrono::steady_clock::now();
 
   auto res = CF_EXPECT(ReadExact(sizeof(uint16_t), timeout),
@@ -218,7 +308,7 @@
   int16_t packet_size = packet_size_slice.read_le<uint16_t>();
 
   auto current_time = std::chrono::steady_clock::now();
-  timeout -= std::chrono::duration_cast<std::chrono::milliseconds>(
+  timeout -= std::chrono::duration_cast<std::chrono::microseconds>(
       current_time - start_time);
   return CF_EXPECT(ReadExact(packet_size, timeout),
                    "Failed to read RF packet payload");
diff --git a/host/commands/casimir_control_server/casimir_controller.h b/host/commands/casimir_control_server/casimir_controller.h
index 3603cd7..6ca7781 100644
--- a/host/commands/casimir_control_server/casimir_controller.h
+++ b/host/commands/casimir_control_server/casimir_controller.h
@@ -38,6 +38,11 @@
 
   Result<void> SetPowerLevel(uint32_t power_level);
 
+  Result<std::tuple<std::vector<uint8_t>, std::string, bool, uint32_t, uint32_t,
+                    uint32_t, double>>
+  SendBroadcast(std::vector<uint8_t> data, std::string type, bool crc,
+                uint8_t bits, uint32_t bitrate, uint32_t timeout, double power);
+
   /*
    * Poll for NFC-A + ISO-DEP
    */
@@ -61,9 +66,9 @@
 
   Result<void> Write(const RfPacketBuilder& rf_packet);
   Result<std::shared_ptr<std::vector<uint8_t>>> ReadExact(
-      size_t size, std::chrono::milliseconds timeout);
+      size_t size, std::chrono::microseconds timeout);
   Result<std::shared_ptr<std::vector<uint8_t>>> ReadRfPacket(
-      std::chrono::milliseconds timeout);
+      std::chrono::microseconds timeout);
 
   SharedFD sock_;
   uint8_t power_level;
diff --git a/host/commands/casimir_control_server/crc.cpp b/host/commands/casimir_control_server/crc.cpp
new file mode 100644
index 0000000..09939af
--- /dev/null
+++ b/host/commands/casimir_control_server/crc.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/casimir_control_server/crc.h"
+
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+namespace {
+static std::vector<uint8_t> Crc16(const std::vector<uint8_t>& data,
+                                  uint16_t initial, bool invert) {
+  uint16_t w_crc = initial;
+
+  for (uint8_t byte : data) {
+    byte ^= (w_crc & 0x00FF);
+    byte ^= (byte << 4) & 0xFF;
+    w_crc = (w_crc >> 8) ^ ((byte << 8) & 0xFFFF) ^ ((byte << 3) & 0xFFFF) ^
+            ((byte >> 4) & 0xFFFF);
+  }
+
+  if (invert) {
+    w_crc = ~w_crc;
+  }
+
+  return {static_cast<uint8_t>(w_crc & 0xFF),
+          static_cast<uint8_t>((w_crc >> 8) & 0xFF)};
+}
+
+static std::vector<uint8_t> Crc16A(const std::vector<uint8_t>& data) {
+  return Crc16(data, 0x6363, false);
+}
+
+static std::vector<uint8_t> Crc16B(const std::vector<uint8_t>& data) {
+  return Crc16(data, 0xFFFF, true);
+}
+}  // namespace
+
+Result<std::vector<uint8_t>> WithCrc16A(const std::vector<uint8_t>& data) {
+  std::vector<uint8_t> newData = data;
+  std::vector<uint8_t> crc = Crc16A(newData);
+  newData.insert(newData.end(), crc.begin(), crc.end());
+  return newData;
+}
+
+Result<std::vector<uint8_t>> WithCrc16B(const std::vector<uint8_t>& data) {
+  std::vector<uint8_t> newData = data;
+  std::vector<uint8_t> crc = Crc16B(newData);
+  newData.insert(newData.end(), crc.begin(), crc.end());
+  return newData;
+}
+
+}  // namespace cuttlefish
diff --git a/host/commands/casimir_control_server/crc.h b/host/commands/casimir_control_server/crc.h
new file mode 100644
index 0000000..da1e8fb
--- /dev/null
+++ b/host/commands/casimir_control_server/crc.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "common/libs/utils/result.h"
+
+namespace cuttlefish {
+
+Result<std::vector<uint8_t>> WithCrc16A(const std::vector<uint8_t>& data);
+Result<std::vector<uint8_t>> WithCrc16B(const std::vector<uint8_t>& data);
+
+}  // namespace cuttlefish
diff --git a/host/commands/casimir_control_server/main.cpp b/host/commands/casimir_control_server/main.cpp
index 6a0f44a..4adcd2e 100644
--- a/host/commands/casimir_control_server/main.cpp
+++ b/host/commands/casimir_control_server/main.cpp
@@ -35,7 +35,10 @@
 using casimircontrolserver::RadioState;
 using casimircontrolserver::SendApduReply;
 using casimircontrolserver::SendApduRequest;
+using casimircontrolserver::SendBroadcastRequest;
+using casimircontrolserver::SendBroadcastResponse;
 using casimircontrolserver::SenderId;
+using casimircontrolserver::TransceiveConfiguration;
 using casimircontrolserver::Void;
 
 using cuttlefish::CasimirController;
@@ -218,6 +221,69 @@
     return ResultToStatus(SendApduResult(request, response));
   }
 
+  Result<void> SendBroadcastResult(const SendBroadcastRequest* request,
+                                   SendBroadcastResponse* response) {
+    // Default configuration values
+    TransceiveConfiguration requestConfig;
+    // Type A
+    requestConfig.set_type("A");
+    // CRC present
+    requestConfig.set_crc(true);
+    // 8 bits in last byte
+    requestConfig.set_bits(8);
+    // 106kbps
+    requestConfig.set_bitrate(106);
+    // No timeout, timeout immediately
+    requestConfig.clear_timeout();
+    // 100% output power
+    requestConfig.set_power(100);
+
+    // Overwrite defaults with provided configuration, if present
+    if (request->has_configuration()) {
+      auto config = request->configuration();
+      if (config.has_type()) {
+        requestConfig.set_type(config.type());
+      }
+      if (config.has_crc()) {
+        requestConfig.set_crc(config.crc());
+      }
+      if (config.has_bits()) {
+        requestConfig.set_bits(config.bits());
+      }
+      if (config.has_bitrate()) {
+        requestConfig.set_bitrate(config.bitrate());
+      }
+      if (config.has_timeout()) {
+        requestConfig.set_timeout(config.timeout());
+      }
+      if (config.has_power()) {
+        requestConfig.set_power(config.power());
+      }
+    }
+
+    if (!device_.has_value()) {
+      device_ = CF_EXPECT(ConnectToCasimir(), "Failed to connect with casimir");
+      CF_EXPECT(Unmute(), "failed to unmute the device");
+    }
+
+    std::vector<uint8_t> requestData =
+        CF_EXPECT(HexToBytes(request->data()),
+                  "Failed to parse input. Must only contain [0-9a-fA-F]");
+
+    CF_EXPECT(device_->SendBroadcast(
+                  requestData, requestConfig.type(), requestConfig.crc(),
+                  requestConfig.bits(), requestConfig.bitrate(),
+                  requestConfig.timeout(), requestConfig.power()),
+              "Failed to send broadcast data");
+
+    return {};  // Success
+  }
+
+  Status SendBroadcast(ServerContext*, const SendBroadcastRequest* request,
+                       SendBroadcastResponse* response) override {
+    return ResultToStatus(SendBroadcastResult(request, response));
+  }
+
   std::optional<CasimirController> device_;
   bool is_radio_on_ = false;
 };
diff --git a/host/commands/host_bugreport/main.cc b/host/commands/host_bugreport/main.cc
index f37d209..b4dcb4d 100644
--- a/host/commands/host_bugreport/main.cc
+++ b/host/commands/host_bugreport/main.cc
@@ -34,6 +34,7 @@
 #include "ziparchive/zip_writer.h"
 
 DEFINE_string(output, "host_bugreport.zip", "Where to write the output");
+DEFINE_bool(include_adb_bugreport, false, "Includes device's `adb bugreport`.");
 
 namespace cuttlefish {
 namespace {
@@ -188,7 +189,7 @@
       }
     }
 
-    {
+    if (FLAGS_include_adb_bugreport) {
       // TODO(b/359657254) Create the `adb bugreport` asynchronously.
       std::string device_br_dir = TempDir() + "/cvd_dbrXXXXXX";
       CF_EXPECTF(mkdtemp(device_br_dir.data()) != nullptr,
diff --git a/host/frontend/webrtc/html_client/client.html b/host/frontend/webrtc/html_client/client.html
index dfecac2..4614758 100644
--- a/host/frontend/webrtc/html_client/client.html
+++ b/host/frontend/webrtc/html_client/client.html
@@ -213,13 +213,13 @@
           <div>
             <div id='keyboard-prompt-text' class='keyboard-text'>
               <div class='function-key-button'>
-                <button id='shift-button' title='Shift' class='modal-button-highlight'>Shift</button>
-                <button id='alt-button' title='Alt' class='modal-button-highlight'>Alt</button>
-                <button id='ctrl-button' title='Ctrl' class='modal-button-highlight'>Ctrl</button>
+                <button id='shift-button' title='Shift' class='modal-button'>Shift</button>
+                <button id='alt-button' title='Alt' class='modal-button'>Alt</button>
+                <button id='ctrl-button' title='Ctrl' class='modal-button'>Ctrl</button>
               </div>
               <hr>
               <div class='keyboard-button'>
-                <button id='tab-button' title='Tab' class='modal-button-highlight'>Tab</button>
+                <button id='tab-button' title='Tab' class='modal-button'>Tab</button>
               </div>
             </div>
           </div>
diff --git a/host/frontend/webrtc/html_client/js/keyboard.js b/host/frontend/webrtc/html_client/js/keyboard.js
index 3a3499e..4175205 100644
--- a/host/frontend/webrtc/html_client/js/keyboard.js
+++ b/host/frontend/webrtc/html_client/js/keyboard.js
@@ -25,7 +25,6 @@
     dc.sendKeyEvent(keyCode, "keyup");
   }
   let button = document.getElementById(buttonName);
-  button.disabled = false;
   button.addEventListener('mousedown', onMouseDown);
   button.addEventListener('mouseup', onMouseUp);
 }
@@ -33,18 +32,21 @@
 function processToggleButton(buttonName, keyCode, dc) {
   let toggle = false;
   function onMouseDown(evt) {
+    const kPrimaryButton = 1;
+    if ((evt.buttons & kPrimaryButton) == 0) {
+      return;
+    }
     toggle = !toggle;
-    if (toggle) dc.sendKeyEvent(keyCode, "keydown");
-    else dc.sendKeyEvent(keyCode, "keyup");
+    if (toggle) {
+      dc.sendKeyEvent(keyCode, "keydown");
+    } else {
+      dc.sendKeyEvent(keyCode, "keyup");
+    }
     this.classList.toggle('active');
   }
 
-  function onMouseUp(evt) {
-  }
   let button = document.getElementById(buttonName);
-  button.disabled = false;
   button.addEventListener('mousedown', onMouseDown);
-  button.addEventListener('mouseup', onMouseUp);
 }
 
 function enableKeyboardRewriteButton(dc) {
diff --git a/host/frontend/webrtc/html_client/style.css b/host/frontend/webrtc/html_client/style.css
index 44d3aab..c485bc6 100644
--- a/host/frontend/webrtc/html_client/style.css
+++ b/host/frontend/webrtc/html_client/style.css
@@ -22,12 +22,14 @@
   --alert-bg: #927836; /* dark yellow */
   --info-bg: #007000; /* dark green */
   --modal-bg: #5f6368ea; /* Semi-transparent Google grey 500 */
-  --modal-button-bg: #e8eaed; /* Google grey 200 */
+  --modal-button-bg: #efefef; /* Google grey 200 */
   --modal-button-shadow: #444444;
-  --modal-button-fg: black;
+  --modal-button-fg: #434343;
   --modal-button-border: black;
   --modal-button-invalid-border: red;
   --modal-button-highlight-bg: #f4cccc; /* light red */
+  --modal-button-toggled-bg: #d9ead3ff; /* light green */
+  --modal-button-toggled-color: #274e13; /* dark green */
   --modal-padding: 20px;
   --bt-label-fg: green;
   --bt-label-invalid-fg: red;
@@ -50,12 +52,14 @@
   --alert-bg: #f3ef9e; /* light yellow */
   --info-bg: #a5d5a5; /* light green */
   --modal-bg: #d9d9d9ea; /* Semi-transparent Google grey 200 */
-  --modal-button-bg: #7b7b7b; /* Google grey 500 */
+  --modal-button-bg: #666666; /* Google grey 500 */
   --modal-button-shadow: #666666;
-  --modal-button-fg: #fafafa;
+  --modal-button-fg: #efefef;
   --modal-button-border: #c4c4c4; /* Google grey 300 */
   --modal-button-invalid-border: #c3413d; /*light red */
   --modal-button-highlight-bg: #a05555; /* dark red-ish */
+  --modal-button-toggled-bg: #d9ead3ff; /* light green */
+  --modal-button-toggled-color: #274e13; /* dark green */
   --bt-label-fg: green;
   --bt-label-invalid-fg: #c3413d; /* light red */
   --bt-action-bg: transparent;
@@ -182,7 +186,6 @@
   background-color: transparent;
 }
 .modal-button, .modal-button-highlight {
-  background:    var(--modal-button-bg);
   border-radius: 10px;
   box-shadow:    1px 1px var(--modal-button-shadow);
   padding:       10px 20px;
@@ -190,6 +193,17 @@
   display:       inline-block;
   font:          normal bold 14px/1 "Open Sans", sans-serif;
   text-align:    center;
+  cursor:        pointer;
+}
+.modal-button {
+  background: var(--modal-button-bg);
+}
+.modal-button-highlight {
+  background: var(--modal-button-highlight-bg);
+}
+.modal-button.active, .modal-button-highlight.active {
+  background-color: var(--modal-button-toggled-bg);
+  color:            var(--modal-button-toggled-color);
 }
 #bluetooth-wizard-mac:valid {
   border: 2px solid var(--modal-button-border);
@@ -207,12 +221,6 @@
   content: 'OK';
   color: var(--bt-label-fg);
 }
-.modal-button {
-  background: var(--modal-button-bg);
-}
-.modal-button-highlight {
-  background: var(--modal-button-highlight-bg);
-}
 #device-details-modal span {
   white-space: pre;
 }
@@ -267,33 +275,6 @@
 .location-button {
   text-align: center;
 }
-#shift-button {
-  padding: 10px 20px;
-  border: 1px solid #ccc;
-  cursor: pointer;
-}
-#shift-button.active {
-  background-color: #0000ff;
-  color: white;
-}
-#ctrl-button {
-  padding: 10px 20px;
-  border: 1px solid #ccc;
-  cursor: pointer;
-}
-#ctrl-button.active {
-  background-color: #0000ff;
-  color: white;
-}
-#alt-button {
-  padding: 10px 20px;
-  border: 1px solid #ccc;
-  cursor: pointer;
-}
-#alt-button.active {
-  background-color: #0000ff;
-  color: white;
-}
 .sensors{
   position: sticky;
   right: 0;
diff --git a/shared/BoardConfig.mk b/shared/BoardConfig.mk
index 3a1e535..01941d5 100644
--- a/shared/BoardConfig.mk
+++ b/shared/BoardConfig.mk
@@ -116,10 +116,7 @@
 endif
 endif
 
-# TODO(b/170639028): Back up TARGET_NO_BOOTLOADER
-__TARGET_NO_BOOTLOADER := $(TARGET_NO_BOOTLOADER)
 include build/make/target/board/BoardConfigMainlineCommon.mk
-TARGET_NO_BOOTLOADER := $(__TARGET_NO_BOOTLOADER)
 
 # For now modules are only blocked in second stage init.
 # If a module ever needs to blocked in first stage init - add a new blocklist to
diff --git a/shared/auto/sepolicy/vhal/hal_vehicle_default.te b/shared/auto/sepolicy/vhal/hal_vehicle_default.te
index 545a76d..6b5b3aa 100644
--- a/shared/auto/sepolicy/vhal/hal_vehicle_default.te
+++ b/shared/auto/sepolicy/vhal/hal_vehicle_default.te
@@ -2,4 +2,7 @@
 carwatchdog_client_domain(hal_vehicle_default)
 binder_use(hal_vehicle_default)
 
+starting_at_board_api(202504, `
+typeattribute hal_vehicle_default unconstrained_vsock_violators;
+')
 allow hal_vehicle_default self:vsock_socket { create connect getopt getattr read write shutdown };
diff --git a/shared/camera/sepolicy/hal_camera_default.te b/shared/camera/sepolicy/hal_camera_default.te
index 8783a44..0bc9ebd 100644
--- a/shared/camera/sepolicy/hal_camera_default.te
+++ b/shared/camera/sepolicy/hal_camera_default.te
@@ -10,6 +10,9 @@
 hal_client_domain(hal_camera_default, hal_thermal)
 
 # Vsocket camera
+starting_at_board_api(202504, `
+typeattribute hal_camera_default unconstrained_vsock_violators;
+')
 allow hal_camera_default self:vsock_socket { accept bind create getopt listen read write };
 
 set_prop(hal_camera_default, vendor_camera_prop)
diff --git a/shared/graphics/sepolicy/hal_graphics_composer_default.te b/shared/graphics/sepolicy/hal_graphics_composer_default.te
index 643c2d9..5626e72 100644
--- a/shared/graphics/sepolicy/hal_graphics_composer_default.te
+++ b/shared/graphics/sepolicy/hal_graphics_composer_default.te
@@ -7,6 +7,11 @@
 
 allow hal_graphics_composer_default hal_graphics_mapper_service:service_manager find;
 
+# inherited from attribute hal_graphics_composer_server
+starting_at_board_api(202504, `
+typeattribute hal_graphics_composer_default unconstrained_vsock_violators;
+')
+
 # Suppress warnings for drm_hwcomposer trying to read some vendor.hwc.*
 # properties as Cuttlefish never configures these properties.
-dontaudit hal_graphics_composer_default default_prop:file read;
\ No newline at end of file
+dontaudit hal_graphics_composer_default default_prop:file read;
diff --git a/shared/sepolicy/product/private/tombstone_transmit.te b/shared/sepolicy/product/private/tombstone_transmit.te
index 289be52..6b0d8cf 100644
--- a/shared/sepolicy/product/private/tombstone_transmit.te
+++ b/shared/sepolicy/product/private/tombstone_transmit.te
@@ -9,4 +9,5 @@
 allow tombstone_transmit self:capability net_admin;
 r_dir_file(tombstone_transmit, tombstone_data_file)
 
+typeattribute tombstone_transmit unconstrained_vsock_violators;
 allow tombstone_transmit self:{ vsock_socket } create_socket_perms_no_ioctl;
diff --git a/shared/sepolicy/system_ext/private/secure_storage_system.te b/shared/sepolicy/system_ext/private/secure_storage_system.te
index 4d7e653..8b85e09 100644
--- a/shared/sepolicy/system_ext/private/secure_storage_system.te
+++ b/shared/sepolicy/system_ext/private/secure_storage_system.te
@@ -28,6 +28,7 @@
 
 #============= storageproxyd_system ==============
 type storageproxyd_system, domain, coredomain;
+typeattribute storageproxyd_system unconstrained_vsock_violators;
 type storageproxyd_system_exec, exec_type, system_file_type, file_type;
 type secure_storage_persist_system_file, file_type, data_file_type, core_data_file_type;
 type secure_storage_system_file, file_type, data_file_type, core_data_file_type;
diff --git a/shared/sepolicy/vendor/hal_light_cuttlefish.te b/shared/sepolicy/vendor/hal_light_cuttlefish.te
index e02f8e0..56ef539 100644
--- a/shared/sepolicy/vendor/hal_light_cuttlefish.te
+++ b/shared/sepolicy/vendor/hal_light_cuttlefish.te
@@ -4,4 +4,7 @@
 type hal_light_cuttlefish_exec, exec_type, vendor_file_type, file_type;
 init_daemon_domain(hal_light_cuttlefish)
 
+starting_at_board_api(202504, `
+typeattribute hal_light_cuttlefish unconstrained_vsock_violators;
+')
 allow hal_light_cuttlefish self:{ socket vsock_socket } { create_socket_perms_no_ioctl listen accept };
diff --git a/shared/sepolicy/vendor/socket_vsock_proxy.te b/shared/sepolicy/vendor/socket_vsock_proxy.te
index 6f72963..65f5409 100644
--- a/shared/sepolicy/vendor/socket_vsock_proxy.te
+++ b/shared/sepolicy/vendor/socket_vsock_proxy.te
@@ -6,6 +6,9 @@
 allow socket_vsock_proxy self:global_capability_class_set { net_admin net_raw };
 allow socket_vsock_proxy self:{ socket vsock_socket } { create getopt read write getattr listen accept bind shutdown };
 
+starting_at_board_api(202504, `
+typeattribute socket_vsock_proxy unconstrained_vsock_violators;
+')
 # TODO: socket returned by accept() has unlabeled context on it. Give it a
 # specific label.
 allow socket_vsock_proxy unlabeled:{ socket vsock_socket } { getopt read write shutdown };
diff --git a/shared/sepolicy/vendor/telephony/libcuttlefish_rild.te b/shared/sepolicy/vendor/telephony/libcuttlefish_rild.te
index 6e2958e..a1e52a3 100644
--- a/shared/sepolicy/vendor/telephony/libcuttlefish_rild.te
+++ b/shared/sepolicy/vendor/telephony/libcuttlefish_rild.te
@@ -10,5 +10,8 @@
 
 get_prop(libcuttlefish_rild, vendor_modem_simulator_ports_prop)
 
+starting_at_board_api(202504, `
+typeattribute libcuttlefish_rild unconstrained_vsock_violators;
+')
 allow libcuttlefish_rild self:{ socket vsock_socket } { create_socket_perms_no_ioctl getattr };
-allow libcuttlefish_rild su:{ socket udp_socket } { create_socket_perms_no_ioctl getattr };
\ No newline at end of file
+allow libcuttlefish_rild su:{ socket udp_socket } { create_socket_perms_no_ioctl getattr };
diff --git a/vsoc_arm/bootloader.mk b/vsoc_arm/bootloader.mk
index ce29443..aa9f9a6 100644
--- a/vsoc_arm/bootloader.mk
+++ b/vsoc_arm/bootloader.mk
@@ -14,7 +14,5 @@
 # limitations under the License.
 #
 
-TARGET_NO_BOOTLOADER := false
-# FIXME: Copying the QEMU bootloader for now, but this should be updated..
-BOARD_PREBUILT_BOOTLOADER := \
-    device/google/cuttlefish_prebuilts/bootloader/crosvm_aarch64/u-boot.bin
+# May be booted using different bootloaders, so don't have the single one.
+TARGET_NO_BOOTLOADER := true
diff --git a/vsoc_arm64/bootloader.mk b/vsoc_arm64/bootloader.mk
index ce29443..aa9f9a6 100644
--- a/vsoc_arm64/bootloader.mk
+++ b/vsoc_arm64/bootloader.mk
@@ -14,7 +14,5 @@
 # limitations under the License.
 #
 
-TARGET_NO_BOOTLOADER := false
-# FIXME: Copying the QEMU bootloader for now, but this should be updated..
-BOARD_PREBUILT_BOOTLOADER := \
-    device/google/cuttlefish_prebuilts/bootloader/crosvm_aarch64/u-boot.bin
+# May be booted using different bootloaders, so don't have the single one.
+TARGET_NO_BOOTLOADER := true
diff --git a/vsoc_arm_minidroid/bootloader.mk b/vsoc_arm_minidroid/bootloader.mk
index 959cd61..aa9f9a6 100644
--- a/vsoc_arm_minidroid/bootloader.mk
+++ b/vsoc_arm_minidroid/bootloader.mk
@@ -14,7 +14,5 @@
 # limitations under the License.
 #
 
-TARGET_NO_BOOTLOADER := false
-# FIXME: Copying the QEMU bootloader for now, but this should be updated..
-BOARD_PREBUILT_BOOTLOADER := \
-    device/google/cuttlefish_prebuilts/bootloader/qemu_arm/u-boot.bin
+# May be booted using different bootloaders, so don't have the single one.
+TARGET_NO_BOOTLOADER := true
diff --git a/vsoc_riscv64/bootloader.mk b/vsoc_riscv64/bootloader.mk
index 427531b..4866ad3 100644
--- a/vsoc_riscv64/bootloader.mk
+++ b/vsoc_riscv64/bootloader.mk
@@ -14,7 +14,5 @@
 # limitations under the License.
 #
 
-TARGET_NO_BOOTLOADER := false
-# Only QEMU is supported for now
-BOARD_PREBUILT_BOOTLOADER := \
-    device/google/cuttlefish_prebuilts/bootloader/qemu_riscv64/u-boot.bin
+# May be booted using different bootloaders, so don't have the single one.
+TARGET_NO_BOOTLOADER := true
diff --git a/vsoc_x86/go/aosp_cf.mk b/vsoc_x86/go/aosp_cf.mk
index 05bdc38..6dd038e 100644
--- a/vsoc_x86/go/aosp_cf.mk
+++ b/vsoc_x86/go/aosp_cf.mk
@@ -25,7 +25,6 @@
 # These packages come from go_defaults.mk
 PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST += \
     system/apex/com.android.tethering.capex \
-    system/app/PlatformCaptivePortalLogin/PlatformCaptivePortalLogin.apk \
     system/etc/permissions/platform_privapp_allowlist_com.android.cellbroadcastservice.xml \
     system/priv-app/CellBroadcastServiceModulePlatform/CellBroadcastServiceModulePlatform.apk \
 
diff --git a/vsoc_x86_64/bootloader.mk b/vsoc_x86_64/bootloader.mk
index 6294ca7..aa9f9a6 100644
--- a/vsoc_x86_64/bootloader.mk
+++ b/vsoc_x86_64/bootloader.mk
@@ -14,6 +14,5 @@
 # limitations under the License.
 #
 
-TARGET_NO_BOOTLOADER := false
-BOARD_PREBUILT_BOOTLOADER := \
-    device/google/cuttlefish_prebuilts/bootloader/crosvm_x86_64/u-boot.rom
+# May be booted using different bootloaders, so don't have the single one.
+TARGET_NO_BOOTLOADER := true