Merge changes I5f6cf12b,I0e669cdb,I5027ceb6 into main
* changes:
Added a function to create command response
Add function to return a ppid of a given pid
Separate run_cvd process information collection from reset
diff --git a/apex/com.google.cf.bt/Android.bp b/apex/com.google.cf.bt/Android.bp
index d8b2337..fa81cd0 100644
--- a/apex/com.google.cf.bt/Android.bp
+++ b/apex/com.google.cf.bt/Android.bp
@@ -33,6 +33,7 @@
soc_specific: true,
binaries: [
"android.hardware.bluetooth-service.default",
+ "android.hardware.bluetooth.finder-service.default",
"bt_vhci_forwarder",
],
prebuilts: [
@@ -40,5 +41,8 @@
"android.hardware.bluetooth.prebuilt.xml",
"com.google.cf.bt.rc",
],
- vintf_fragments: [":manifest_android.hardware.bluetooth-service.default.xml"],
+ vintf_fragments: [
+ ":manifest_android.hardware.bluetooth-service.default.xml",
+ ":manifest_android.hardware.bluetooth.finder-service.default.xml",
+ ],
}
diff --git a/apex/com.google.cf.bt/com.google.cf.bt.rc b/apex/com.google.cf.bt/com.google.cf.bt.rc
index 8dca890..7160438 100644
--- a/apex/com.google.cf.bt/com.google.cf.bt.rc
+++ b/apex/com.google.cf.bt/com.google.cf.bt.rc
@@ -11,3 +11,9 @@
user bluetooth
group bluetooth net_admin net_bt_admin
capabilities NET_ADMIN
+
+service bt_finder /apex/com.google.cf.bt/bin/hw/android.hardware.bluetooth.finder-service.default
+ class hal
+ user bluetooth
+ group bluetooth net_admin net_bt_admin
+ capabilities NET_ADMIN
diff --git a/apex/com.google.cf.bt/file_contexts b/apex/com.google.cf.bt/file_contexts
index 101c5b3..1b8078b 100644
--- a/apex/com.google.cf.bt/file_contexts
+++ b/apex/com.google.cf.bt/file_contexts
@@ -1,4 +1,5 @@
-(/.*)? u:object_r:vendor_file:s0
-/bin/hw/android.hardware.bluetooth-service.default u:object_r:hal_bluetooth_btlinux_exec:s0
-/bin/bt_vhci_forwarder u:object_r:bt_vhci_forwarder_exec:s0
-/etc/permissions(/.*)? u:object_r:vendor_configs_file:s0
+(/.*)? u:object_r:vendor_file:s0
+/bin/hw/android.hardware.bluetooth-service.default u:object_r:hal_bluetooth_btlinux_exec:s0
+/bin/hw/android.hardware.bluetooth.finder-service.default u:object_r:hal_bluetooth_btlinux_exec:s0
+/bin/bt_vhci_forwarder u:object_r:bt_vhci_forwarder_exec:s0
+/etc/permissions(/.*)? u:object_r:vendor_configs_file:s0
diff --git a/build/Android.bp b/build/Android.bp
index 3ff4743..38c159c 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -185,6 +185,7 @@
"webrtc_client.html",
"webrtc_rootcanal.js",
"webrtc_location.js",
+ "webrtc_touch.js",
"webrtc_server.crt",
"webrtc_server.key",
"webrtc_server.p12",
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index 53a5012..0be0ccf 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -27,6 +27,7 @@
#include <sstream>
#include <unordered_map>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
@@ -41,6 +42,7 @@
#include "common/libs/utils/contains.h"
#include "common/libs/utils/files.h"
#include "common/libs/utils/flag_parser.h"
+#include "common/libs/utils/json.h"
#include "common/libs/utils/network.h"
#include "host/commands/assemble_cvd/alloc.h"
#include "host/commands/assemble_cvd/boot_config.h"
@@ -51,6 +53,8 @@
#include "host/commands/assemble_cvd/graphics_flags.h"
#include "host/commands/assemble_cvd/misc_info.h"
#include "host/commands/assemble_cvd/touchpad.h"
+#include "host/commands/cvd/parser/load_configs_parser.h"
+#include "host/libs/config/config_flag.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/display.h"
#include "host/libs/config/esp.h"
@@ -454,6 +458,10 @@
"Declaring that device is snapshot'able and runs with only "
"supported ones.");
+DEFINE_vec(mcu_config_path, CF_DEFAULTS_MCU_CONFIG_PATH,
+ "configuration file for the MCU emulator");
+
+
DECLARE_string(assembly_dir);
DECLARE_string(boot_image);
DECLARE_string(system_image_dir);
@@ -1119,6 +1127,8 @@
std::vector<std::string> device_external_network_vec =
CF_EXPECT(GET_FLAG_STR_VALUE(device_external_network));
+ std::vector<std::string> mcu_config_vec = CF_EXPECT(GET_FLAG_STR_VALUE(mcu_config_path));
+
std::string default_enable_sandbox = "";
std::string default_enable_virtiofs = "";
std::string comma_str = "";
@@ -1617,6 +1627,17 @@
"TODO(b/286284441): slirp only works on QEMU");
instance.set_external_network_mode(external_network_mode);
+ if (!mcu_config_vec[instance_index].empty()) {
+ auto mcu_cfg_path = mcu_config_vec[instance_index];
+ CF_EXPECT(FileExists(mcu_cfg_path), "MCU config file does not exist");
+ std::string file_content;
+ using android::base::ReadFileToString;
+ CF_EXPECT(ReadFileToString(mcu_cfg_path.c_str(), &file_content,
+ /* follow_symlinks */ true),
+ "Failed to read mcu config file");
+ instance.set_mcu(CF_EXPECT(ParseJson(file_content), "Failed parsing JSON file"));
+ }
+
instance_index++;
} // end of num_instances loop
@@ -1795,6 +1816,14 @@
SetCommandLineOptionWithMode("cpus", "1", SET_FLAGS_DEFAULT);
}
+void SetDefaultFlagsForMcu() {
+ auto path = DefaultHostArtifactsPath("etc/mcu_config.json");
+ if (!CanAccess(path, R_OK)) {
+ return;
+ }
+ SetCommandLineOptionWithMode("mcu_config_path", path.c_str(), SET_FLAGS_DEFAULT);
+}
+
void SetDefaultFlagsForOpenwrt(Arch target_arch) {
if (target_arch == Arch::X86_64) {
SetCommandLineOptionWithMode(
@@ -1893,6 +1922,8 @@
SetDefaultFlagsForOpenwrt(guest_configs[0].target_arch);
+ SetDefaultFlagsForMcu();
+
// Set the env variable to empty (in case the caller passed a value for it).
unsetenv(kCuttlefishConfigEnvVarName);
diff --git a/host/commands/assemble_cvd/flags_defaults.h b/host/commands/assemble_cvd/flags_defaults.h
index 3194151..a42d166 100644
--- a/host/commands/assemble_cvd/flags_defaults.h
+++ b/host/commands/assemble_cvd/flags_defaults.h
@@ -224,3 +224,6 @@
// Metrics default parameters
// TODO: Defined twice , please remove redundant definitions
#define CF_DEFAULTS_REPORT_ANONYMOUS_USAGE_STATS CF_DEFAULTS_DYNAMIC_STRING
+
+// MCU emulator default configuration path
+#define CF_DEFAULTS_MCU_CONFIG_PATH CF_DEFAULTS_DYNAMIC_STRING
diff --git a/host/commands/run_cvd/Android.bp b/host/commands/run_cvd/Android.bp
index 467b567..a358b04 100644
--- a/host/commands/run_cvd/Android.bp
+++ b/host/commands/run_cvd/Android.bp
@@ -45,6 +45,7 @@
"launch/webrtc_recorder.cpp",
"launch/streamer.cpp",
"launch/netsim_server.cpp",
+ "launch/mcu.cpp",
"main.cc",
"reporting.cpp",
"server_loop.cpp",
diff --git a/host/commands/run_cvd/launch/launch.h b/host/commands/run_cvd/launch/launch.h
index abb4566..ad8dcbc 100644
--- a/host/commands/run_cvd/launch/launch.h
+++ b/host/commands/run_cvd/launch/launch.h
@@ -131,4 +131,9 @@
launchStreamerComponent();
fruit::Component<WebRtcRecorder> WebRtcRecorderComponent();
+
+fruit::Component<
+ fruit::Required<const CuttlefishConfig,
+ const CuttlefishConfig::InstanceSpecific, LogTeeCreator>>
+McuComponent();
} // namespace cuttlefish
diff --git a/host/commands/run_cvd/launch/mcu.cpp b/host/commands/run_cvd/launch/mcu.cpp
new file mode 100644
index 0000000..96e426e
--- /dev/null
+++ b/host/commands/run_cvd/launch/mcu.cpp
@@ -0,0 +1,125 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <unordered_set>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <fruit/fruit.h>
+#include <json/json.h>
+
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/result.h"
+#include "host/commands/run_cvd/launch/launch.h"
+#include "host/commands/run_cvd/launch/log_tee_creator.h"
+#include "host/libs/config/command_source.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
+
+
+// timeout for the MCU channels to be created after the start command is issued
+#define MCU_START_TIMEOUT 30
+
+namespace cuttlefish {
+namespace {
+
+class Mcu : public vm_manager::VmmDependencyCommand {
+ public:
+ INJECT(Mcu(const CuttlefishConfig::InstanceSpecific& instance,
+ LogTeeCreator& log_tee))
+ :instance_(instance), log_tee_(log_tee) {}
+
+ Result<void> ResultSetup() override {
+ if (!Enabled()) {
+ return {};
+ }
+
+ mcu_dir_ = instance_.PerInstanceInternalPath("/mcu/");
+ CF_EXPECT(EnsureDirectoryExists(mcu_dir_),
+ "MCU directory cannot be created.");
+ return {};
+ }
+
+ // CommandSource
+ Result<std::vector<MonitorCommand>> Commands() override {
+ if (!Enabled()) {
+ return {};
+ }
+
+ auto start = instance_.mcu()["start-cmd"];
+ CF_EXPECT(start.type() == Json::arrayValue,
+ "mcu: config: start-cmd: array expected");
+ CF_EXPECT(start.size() > 0, "mcu: config: empty start-cmd");
+ Command command(start[0].asString());
+
+ std::string wdir = "${wdir}";
+ for (unsigned int i = 1; i < start.size(); i++) {
+ auto param = start[i].asString();
+ param = android::base::StringReplace(param, "${wdir}", mcu_dir_, true);
+ command.AddParameter(param);
+ }
+
+ std::vector<MonitorCommand> commands;
+ commands.emplace_back(CF_EXPECT(log_tee_.CreateLogTee(command, "mcu")));
+ commands.emplace_back(std::move(command));
+ return commands;
+ }
+
+ // SetupFeature
+ std::string Name() const override { return "MCU"; }
+ bool Enabled() const override {
+ return instance_.mcu().type() != Json::nullValue;
+ }
+
+ // StatusCheckCommandSource
+ Result<void> WaitForAvailability() const {
+ if (!Enabled()) {
+ return {};
+ }
+
+ auto control = instance_.mcu()["control"]["path"];
+ if (control.type() == Json::stringValue) {
+ CF_EXPECT(WaitForFile(mcu_dir_ + "/" + control.asString(),
+ MCU_START_TIMEOUT));
+ }
+ auto uart0 = instance_.mcu()["uart0"]["path"];
+ if (uart0.type() == Json::stringValue) {
+ CF_EXPECT(WaitForFile(mcu_dir_ + "/" + uart0.asString(),
+ MCU_START_TIMEOUT));
+ }
+ return {};
+ }
+
+ private:
+ std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
+
+ std::string mcu_dir_;
+ const CuttlefishConfig::InstanceSpecific& instance_;
+ LogTeeCreator& log_tee_;
+};
+
+} // namespace
+
+fruit::Component<
+ fruit::Required<const CuttlefishConfig,
+ const CuttlefishConfig::InstanceSpecific, LogTeeCreator>>
+McuComponent() {
+ return fruit::createComponent()
+ .addMultibinding<vm_manager::VmmDependencyCommand, Mcu>()
+ .addMultibinding<CommandSource, Mcu>()
+ .addMultibinding<SetupFeature, Mcu>();
+}
+
+} // namespace cuttlefish
diff --git a/host/commands/run_cvd/main.cc b/host/commands/run_cvd/main.cc
index a7e2c15..9e415f0 100644
--- a/host/commands/run_cvd/main.cc
+++ b/host/commands/run_cvd/main.cc
@@ -159,6 +159,7 @@
.install(AutoCmd<MetricsService>::Component)
.install(OpenwrtControlServerComponent)
.install(AutoCmd<Pica>::Component)
+ .install(McuComponent)
.install(RootCanalComponent)
.install(AutoCmd<Casimir>::Component)
.install(NetsimServerComponent)
diff --git a/host/frontend/adb_connector/adb_connection_maintainer.cpp b/host/frontend/adb_connector/adb_connection_maintainer.cpp
index 20d92a4..e877771 100644
--- a/host/frontend/adb_connector/adb_connection_maintainer.cpp
+++ b/host/frontend/adb_connector/adb_connection_maintainer.cpp
@@ -15,13 +15,14 @@
*/
#include "host/frontend/adb_connector/adb_connection_maintainer.h"
+#include <android-base/logging.h>
+#include <android-base/strings.h>
#include <cctype>
#include <iomanip>
+#include <memory>
#include <sstream>
#include <string>
-#include <memory>
#include <vector>
-#include <android-base/logging.h>
#include <unistd.h>
@@ -54,10 +55,15 @@
return MakeMessage("host:disconnect:" + address);
}
+std::string MakeGetStateMessage(const std::string& address) {
+ return MakeMessage("host-serial:" + address + ":get-state");
+}
+
// Response will either be OKAY or FAIL
constexpr char kAdbOkayStatusResponse[] = "OKAY";
constexpr std::size_t kAdbStatusResponseLength =
sizeof kAdbOkayStatusResponse - 1;
+constexpr std::string_view kAdbUnauthorizedMsg = "device unauthorized.";
// adb sends the length of what is to follow as a 4 characters string of hex
// digits
constexpr std::size_t kAdbMessageLengthLength = 4;
@@ -140,6 +146,38 @@
return std::stoi(uptime_str);
}
+// Check if the connection state is waiting for authorization. This function
+// returns true only when explicitly receiving the unauthorized error message,
+// while returns false for all the other error cases because we need to call
+// AdbConnect() again rather than waiting for users' authorization.
+bool WaitForAdbAuthorization(const std::string& address) {
+ auto sock = SharedFD::SocketLocalClient(kAdbDaemonPort, SOCK_STREAM);
+ // Socket doesn't open, so we should not block at waiting for authorization.
+ if (!sock->IsOpen()) {
+ LOG(WARNING) << "failed to open adb connection: " << sock->StrError();
+ return false;
+ }
+
+ if (!SendAll(sock, MakeGetStateMessage(address))) {
+ LOG(WARNING) << "failed to send get state message to adb daemon";
+ return false;
+ }
+
+ const std::string status = RecvAll(sock, kAdbStatusResponseLength);
+ // Stop waiting because the authorization check passed.
+ if (status == kAdbOkayStatusResponse) {
+ return false;
+ }
+
+ const auto response = RecvAdbResponse(sock);
+ // Do not wait for authorization due to failure to receive an adb response.
+ if (response.empty()) {
+ return false;
+ }
+
+ return android::base::StartsWith(response, kAdbUnauthorizedMsg);
+}
+
// There needs to be a gap between the adb commands, the daemon isn't able to
// handle the avalanche of requests we would be sending without a sleep. Five
// seconds is much larger than seems necessary so we should be more than okay.
@@ -152,6 +190,13 @@
}
LOG(DEBUG) << "adb connect message for " << address << " successfully sent";
sleep(kAdbCommandGapTime);
+
+ while (WaitForAdbAuthorization(address)) {
+ LOG(WARNING) << "adb unauthorized, retrying";
+ sleep(kAdbCommandGapTime);
+ }
+ LOG(DEBUG) << "adb connected to " << address;
+ sleep(kAdbCommandGapTime);
}
void WaitForAdbDisconnection(const std::string& address) {
diff --git a/host/frontend/webrtc/connection_observer.cpp b/host/frontend/webrtc/connection_observer.cpp
index 6a65d36..653c6cc 100644
--- a/host/frontend/webrtc/connection_observer.cpp
+++ b/host/frontend/webrtc/connection_observer.cpp
@@ -72,6 +72,7 @@
if (kernel_log_subscription_id_ != -1) {
kernel_log_events_handler_->Unsubscribe(kernel_log_subscription_id_);
}
+ input_connector_.OnDisconnectedSource(this);
}
void OnConnected() override {
@@ -88,12 +89,11 @@
bool down, int size) {
std::vector<MultitouchSlot> slots(size);
for (int i = 0; i < size; i++) {
- slots[i].slot = slot[i].asInt();
slots[i].id = id[i].asInt();
slots[i].x = x[i].asInt();
slots[i].y = y[i].asInt();
}
- input_connector_.SendMultiTouchEvent(device_label, slots, down);
+ input_connector_.SendMultiTouchEvent(this, device_label, slots, down);
}
void OnKeyboardEvent(uint16_t code, bool down) override {
diff --git a/host/frontend/webrtc/html_client/Android.bp b/host/frontend/webrtc/html_client/Android.bp
index f5efb6e..b389f2d 100644
--- a/host/frontend/webrtc/html_client/Android.bp
+++ b/host/frontend/webrtc/html_client/Android.bp
@@ -86,3 +86,10 @@
filename: "location.js",
sub_dir: "webrtc/assets/js",
}
+
+prebuilt_usr_share_host {
+ name: "webrtc_touch.js",
+ src: "js/touch.js",
+ filename: "touch.js",
+ sub_dir: "webrtc/assets/js",
+}
diff --git a/host/frontend/webrtc/html_client/client.html b/host/frontend/webrtc/html_client/client.html
index fa65875..764c8d2 100644
--- a/host/frontend/webrtc/html_client/client.html
+++ b/host/frontend/webrtc/html_client/client.html
@@ -252,6 +252,7 @@
<script src="js/rootcanal.js"></script>
<script src="js/cf_webrtc.js" type="module"></script>
<script src="js/controls.js"></script>
+ <script src="js/touch.js"></script>
<script src="js/app.js"></script>
<template id="display-template">
<div class="device-display">
diff --git a/host/frontend/webrtc/html_client/js/app.js b/host/frontend/webrtc/html_client/js/app.js
index db9dee2..44395ea 100644
--- a/host/frontend/webrtc/html_client/js/app.js
+++ b/host/frontend/webrtc/html_client/js/app.js
@@ -965,153 +965,7 @@
}
#addMouseTracking(displayDeviceVideo) {
- let $this = this;
- let mouseIsDown = false;
- let mouseCtx = {
- down: false,
- touchIdSlotMap: new Map(),
- touchSlots: [],
- };
- function onStartDrag(e) {
- // Can't prevent event default behavior to allow the element gain focus
- // when touched and start capturing keyboard input in the parent.
- // console.debug("mousedown at " + e.pageX + " / " + e.pageY);
- mouseCtx.down = true;
-
- $this.#sendEventUpdate(mouseCtx, e);
- }
-
- function onEndDrag(e) {
- // Can't prevent event default behavior to allow the element gain focus
- // when touched and start capturing keyboard input in the parent.
- // console.debug("mouseup at " + e.pageX + " / " + e.pageY);
- mouseCtx.down = false;
-
- $this.#sendEventUpdate(mouseCtx, e);
- }
-
- function onContinueDrag(e) {
- // Can't prevent event default behavior to allow the element gain focus
- // when touched and start capturing keyboard input in the parent.
- // console.debug("mousemove at " + e.pageX + " / " + e.pageY + ", down=" +
- // mouseIsDown);
- if (mouseCtx.down) {
- $this.#sendEventUpdate(mouseCtx, e);
- }
- }
-
- if (window.PointerEvent) {
- displayDeviceVideo.addEventListener('pointerdown', onStartDrag);
- displayDeviceVideo.addEventListener('pointermove', onContinueDrag);
- displayDeviceVideo.addEventListener('pointerup', onEndDrag);
- } else if (window.TouchEvent) {
- displayDeviceVideo.addEventListener('touchstart', onStartDrag);
- displayDeviceVideo.addEventListener('touchmove', onContinueDrag);
- displayDeviceVideo.addEventListener('touchend', onEndDrag);
- } else if (window.MouseEvent) {
- displayDeviceVideo.addEventListener('mousedown', onStartDrag);
- displayDeviceVideo.addEventListener('mousemove', onContinueDrag);
- displayDeviceVideo.addEventListener('mouseup', onEndDrag);
- }
- }
-
- #sendEventUpdate(ctx, e) {
- let eventType = e.type.substring(0, 5);
-
- // The <video> element:
- const deviceDisplay = e.target;
-
- // Before the first video frame arrives there is no way to know width and
- // height of the device's screen, so turn every click into a click at 0x0.
- // A click at that position is not more dangerous than anywhere else since
- // the user is clicking blind anyways.
- const videoWidth = deviceDisplay.videoWidth ? deviceDisplay.videoWidth : 1;
- const elementWidth =
- deviceDisplay.offsetWidth ? deviceDisplay.offsetWidth : 1;
- const scaling = videoWidth / elementWidth;
-
- let xArr = [];
- let yArr = [];
- let idArr = [];
- let slotArr = [];
-
- if (eventType == 'mouse' || eventType == 'point') {
- xArr.push(e.offsetX);
- yArr.push(e.offsetY);
-
- let thisId = -1;
- if (eventType == 'point') {
- thisId = e.pointerId;
- }
-
- slotArr.push(0);
- idArr.push(thisId);
- } else if (eventType == 'touch') {
- // touchstart: list of touch points that became active
- // touchmove: list of touch points that changed
- // touchend: list of touch points that were removed
- let changes = e.changedTouches;
- let rect = e.target.getBoundingClientRect();
- for (let i = 0; i < changes.length; i++) {
- xArr.push(changes[i].pageX - rect.left);
- yArr.push(changes[i].pageY - rect.top);
- if (ctx.touchIdSlotMap.has(changes[i].identifier)) {
- let slot = ctx.touchIdSlotMap.get(changes[i].identifier);
-
- slotArr.push(slot);
- if (e.type == 'touchstart') {
- // error
- console.error('touchstart when already have slot');
- return;
- } else if (e.type == 'touchmove') {
- idArr.push(changes[i].identifier);
- } else if (e.type == 'touchend') {
- ctx.touchSlots[slot] = false;
- ctx.touchIdSlotMap.delete(changes[i].identifier);
- idArr.push(-1);
- }
- } else {
- if (e.type == 'touchstart') {
- let slot = -1;
- for (let j = 0; j < ctx.touchSlots.length; j++) {
- if (!ctx.touchSlots[j]) {
- slot = j;
- break;
- }
- }
- if (slot == -1) {
- slot = ctx.touchSlots.length;
- ctx.touchSlots.push(true);
- }
- slotArr.push(slot);
- ctx.touchSlots[slot] = true;
- ctx.touchIdSlotMap.set(changes[i].identifier, slot);
- idArr.push(changes[i].identifier);
- } else if (e.type == 'touchmove') {
- // error
- console.error('touchmove when no slot');
- return;
- } else if (e.type == 'touchend') {
- // error
- console.error('touchend when no slot');
- return;
- }
- }
- }
- }
-
- for (let i = 0; i < xArr.length; i++) {
- xArr[i] = Math.trunc(xArr[i] * scaling);
- yArr[i] = Math.trunc(yArr[i] * scaling);
- }
-
- // NOTE: Rotation is handled automatically because the CSS rotation through
- // transforms also rotates the coordinates of events on the object.
-
- const device_label = deviceDisplay.id;
-
- this.#deviceConnection.sendMultiTouch(
- {idArr, xArr, yArr, down: ctx.down, slotArr, device_label});
+ trackPointerEvents(displayDeviceVideo, this.#deviceConnection);
}
#updateDisplayVisibility(displayId, powerMode) {
diff --git a/host/frontend/webrtc/html_client/js/cf_webrtc.js b/host/frontend/webrtc/html_client/js/cf_webrtc.js
index 1b93bcf..788c154 100644
--- a/host/frontend/webrtc/html_client/js/cf_webrtc.js
+++ b/host/frontend/webrtc/html_client/js/cf_webrtc.js
@@ -287,16 +287,16 @@
// TODO (b/124121375): This should probably be an array of pointer events and
// have different properties.
- sendMultiTouch({idArr, xArr, yArr, down, slotArr, device_label}) {
- this.#sendJsonInput({
- type: 'multi-touch',
- id: idArr,
- x: xArr,
- y: yArr,
- down: down ? 1 : 0,
- slot: slotArr,
- device_label: device_label,
- });
+ sendMultiTouch({idArr, xArr, yArr, down, device_label}) {
+ let events = {
+ type: 'multi-touch',
+ id: idArr,
+ x: xArr,
+ y: yArr,
+ down: down ? 1 : 0,
+ device_label: device_label,
+ };
+ this.#sendJsonInput(events);
}
sendKeyEvent(code, type) {
diff --git a/host/frontend/webrtc/html_client/js/touch.js b/host/frontend/webrtc/html_client/js/touch.js
new file mode 100644
index 0000000..38213e3
--- /dev/null
+++ b/host/frontend/webrtc/html_client/js/touch.js
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+function trackPointerEvents(videoElement, dc) {
+ let activePointers = new Set();
+
+ function onPointerDown(e) {
+ // Can't prevent event default behavior to allow the element gain focus
+ // when touched and start capturing keyboard input in the parent.
+ activePointers.add(e.pointerId);
+ sendEventUpdate(dc, e.target, [{x: e.offsetX, y: e.offsetY, id: e.pointerId}], true);
+ }
+
+ function onPointerUp(e) {
+ // Can't prevent event default behavior to allow the element gain focus
+ // when touched and start capturing keyboard input in the parent.
+ const wasDown = activePointers.delete(e.pointerId);
+ if (!wasDown) {
+ return;
+ }
+ sendEventUpdate(dc, e.target, [{x: e.offsetX, y: e.offsetY, id: e.pointerId}], false);
+ }
+
+ function onPointerMove(e) {
+ // Can't prevent event default behavior to allow the element gain focus
+ // when touched and start capturing keyboard input in the parent.
+ if (!activePointers.has(e.pointerId)) {
+ // This is just a mouse move, not a drag
+ return;
+ }
+ sendEventUpdate(dc, e.target, [{x: e.offsetX, y: e.offsetY, id: e.pointerId}], true);
+ }
+
+ videoElement.addEventListener('pointerdown', onPointerDown);
+ videoElement.addEventListener('pointermove', onPointerMove);
+ videoElement.addEventListener('pointerup', onPointerUp);
+ videoElement.addEventListener('pointerleave', onPointerUp);
+ videoElement.addEventListener('pointercancel', onPointerUp);
+}
+
+function sendEventUpdate(dc, deviceDisplay, evs /*[{x, y, id}]*/, down) {
+ if (evs.length == 0) {
+ return;
+ }
+
+ // Before the first video frame arrives there is no way to know width and
+ // height of the device's screen, so turn every click into a click at 0x0.
+ // A click at that position is not more dangerous than anywhere else since
+ // the user is clicking blind anyways.
+ const videoWidth = deviceDisplay.videoWidth ? deviceDisplay.videoWidth : 1;
+ const elementWidth =
+ deviceDisplay.offsetWidth ? deviceDisplay.offsetWidth : 1;
+ const scaling = videoWidth / elementWidth;
+
+ let xArr = [];
+ let yArr = [];
+ let idArr = [];
+
+ for (const e of evs) {
+ xArr.push(e.x);
+ yArr.push(e.y);
+ idArr.push(e.id);
+ }
+
+ for (const i in xArr) {
+ xArr[i] = Math.trunc(xArr[i] * scaling);
+ yArr[i] = Math.trunc(yArr[i] * scaling);
+ }
+
+ // NOTE: Rotation is handled automatically because the CSS rotation through
+ // transforms also rotates the coordinates of events on the object.
+
+ const device_label = deviceDisplay.id;
+
+ dc.sendMultiTouch(
+ {idArr, xArr, yArr, down: down, device_label});
+}
+
diff --git a/host/frontend/webrtc/libdevice/data_channels.cpp b/host/frontend/webrtc/libdevice/data_channels.cpp
index 719fdca..113013c 100644
--- a/host/frontend/webrtc/libdevice/data_channels.cpp
+++ b/host/frontend/webrtc/libdevice/data_channels.cpp
@@ -91,7 +91,7 @@
std::unique_ptr<Json::CharReader> json_reader(builder.newCharReader());
std::string errorMessage;
auto str = msg.data.cdata<char>();
- if (!json_reader->parse(str, str + size, &evt, &errorMessage) < 0) {
+ if (!json_reader->parse(str, str + size, &evt, &errorMessage)) {
LOG(ERROR) << "Received invalid JSON object over input channel: "
<< errorMessage;
return;
@@ -126,7 +126,6 @@
{"down", Json::ValueType::intValue},
{"x", Json::ValueType::arrayValue},
{"y", Json::ValueType::arrayValue},
- {"slot", Json::ValueType::arrayValue},
{"device_label", Json::ValueType::stringValue}});
if (!result.ok()) {
LOG(ERROR) << result.error().FormatForEnv();
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index 4468fa3..b41b9cc 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -455,6 +455,8 @@
// Whether this instance should start a netsim instance
bool start_netsim() const;
+ const Json::Value& mcu() const;
+
enum class APBootFlow {
// Not starting AP at all (for example not the 1st instance)
None,
@@ -695,6 +697,7 @@
void set_start_casimir(bool start);
void set_start_pica(bool start);
void set_start_netsim(bool start);
+ void set_mcu(const Json::Value &v);
void set_ap_boot_flow(InstanceSpecific::APBootFlow flow);
void set_crosvm_use_balloon(const bool use_balloon);
void set_crosvm_use_rng(const bool use_rng);
diff --git a/host/libs/config/cuttlefish_config_instance.cpp b/host/libs/config/cuttlefish_config_instance.cpp
index 74a234e..32271ab 100644
--- a/host/libs/config/cuttlefish_config_instance.cpp
+++ b/host/libs/config/cuttlefish_config_instance.cpp
@@ -1554,6 +1554,14 @@
return (*Dictionary())[kStartNetsim].asBool();
}
+static constexpr char kMcu[] = "mcu";
+void CuttlefishConfig::MutableInstanceSpecific::set_mcu(const Json::Value& cfg) {
+ (*Dictionary())[kMcu] = cfg;
+}
+const Json::Value& CuttlefishConfig::InstanceSpecific::mcu() const {
+ return (*Dictionary())[kMcu];
+}
+
static constexpr char kApBootFlow[] = "ap_boot_flow";
void CuttlefishConfig::MutableInstanceSpecific::set_ap_boot_flow(APBootFlow flow) {
(*Dictionary())[kApBootFlow] = static_cast<int>(flow);
diff --git a/host/libs/confui/host_virtual_input.cc b/host/libs/confui/host_virtual_input.cc
index 46ac6d4..54b878d 100644
--- a/host/libs/confui/host_virtual_input.cc
+++ b/host/libs/confui/host_virtual_input.cc
@@ -47,10 +47,11 @@
}
Result<void> HostVirtualInput::SendMultiTouchEvent(
- const std::string& device_label, const std::vector<MultitouchSlot>& slots,
- bool down) {
+ void* source, const std::string& device_label,
+ const std::vector<MultitouchSlot>& slots, bool down) {
if (!IsConfUiActive()) {
- return android_mode_input_.SendMultiTouchEvent(device_label, slots, down);
+ return android_mode_input_.SendMultiTouchEvent(source, device_label, slots,
+ down);
}
for (auto& slot: slots) {
if (down) {
@@ -64,6 +65,10 @@
return {};
}
+void HostVirtualInput::OnDisconnectedSource(void* source) {
+ android_mode_input_.OnDisconnectedSource(source);
+}
+
Result<void> HostVirtualInput::SendKeyboardEvent(uint16_t code, bool down) {
if (!IsConfUiActive()) {
return android_mode_input_.SendKeyboardEvent(code, down);
diff --git a/host/libs/confui/host_virtual_input.h b/host/libs/confui/host_virtual_input.h
index a7651bc..fe5528c 100644
--- a/host/libs/confui/host_virtual_input.h
+++ b/host/libs/confui/host_virtual_input.h
@@ -45,9 +45,11 @@
// InputConnector implementation.
Result<void> SendTouchEvent(const std::string& device_label, int x, int y,
bool down) override;
- Result<void> SendMultiTouchEvent(const std::string& device_label,
+ Result<void> SendMultiTouchEvent(void* source,
+ const std::string& device_label,
const std::vector<MultitouchSlot>& slots,
bool down) override;
+ void OnDisconnectedSource(void* source) override;
Result<void> SendKeyboardEvent(uint16_t code, bool down) override;
Result<void> SendRotaryEvent(int pixels) override;
Result<void> SendSwitchesEvent(uint16_t code, bool state) override;
diff --git a/host/libs/input_connector/input_connector.h b/host/libs/input_connector/input_connector.h
index c068f4b..0efbee0 100644
--- a/host/libs/input_connector/input_connector.h
+++ b/host/libs/input_connector/input_connector.h
@@ -24,7 +24,6 @@
struct MultitouchSlot {
int32_t id;
- int32_t slot;
int32_t x;
int32_t y;
};
@@ -34,9 +33,19 @@
virtual ~InputConnector() = default;
virtual Result<void> SendTouchEvent(const std::string& display, int x, int y,
bool down) = 0;
+ // The source parameter is used to differentiate between events coming from
+ // different sources with the same id. For example when multiple clients are
+ // connected and sending touch events at the same time.
virtual Result<void> SendMultiTouchEvent(
- const std::string& device_label, const std::vector<MultitouchSlot>& slots,
- bool down) = 0;
+ void* source, const std::string& device_label,
+ const std::vector<MultitouchSlot>& slots, bool down) = 0;
+ // The InputConnector holds state of on-going touch contacts. Event sources
+ // that can produce multi touch events should call this function when it's
+ // known they won't produce any more events (because, for example, the
+ // streaming client disconnected) to make sure no stale touch contacts remain.
+ // This addresses issues arising from clients disconnecting in the middle of a
+ // touch action.
+ virtual void OnDisconnectedSource(void* source) = 0;
virtual Result<void> SendKeyboardEvent(uint16_t code, bool down) = 0;
virtual Result<void> SendRotaryEvent(int pixels) = 0;
virtual Result<void> SendSwitchesEvent(uint16_t code, bool state) = 0;
diff --git a/host/libs/input_connector/socket_input_connector.cpp b/host/libs/input_connector/socket_input_connector.cpp
index cd2f81d..8b4944c 100644
--- a/host/libs/input_connector/socket_input_connector.cpp
+++ b/host/libs/input_connector/socket_input_connector.cpp
@@ -127,9 +127,78 @@
return {};
}
-struct TouchDevice {
- std::unique_ptr<InputSocket> socket;
- std::set<int32_t> active_slots;
+class TouchDevice {
+ public:
+ TouchDevice(std::unique_ptr<InputSocket> s) : socket_(std::move(s)) {}
+
+ Result<void> WriteEvents(std::unique_ptr<InputEventsBuffer> buffer) {
+ return socket_->WriteEvents(std::move(buffer));
+ }
+
+ bool HasSlot(void* source, int32_t id) {
+ std::lock_guard<std::mutex> lock(slots_mtx_);
+ return slots_by_source_and_id_.find({source, id}) !=
+ slots_by_source_and_id_.end();
+ }
+
+ int32_t GetOrAcquireSlot(void* source, int32_t id) {
+ std::lock_guard<std::mutex> lock(slots_mtx_);
+ auto slot_it = slots_by_source_and_id_.find({source, id});
+ if (slot_it != slots_by_source_and_id_.end()) {
+ return slot_it->second;
+ }
+ return slots_by_source_and_id_[std::make_pair(source, id)] = UseNewSlot();
+ }
+
+ void ReleaseSlot(void* source, int32_t id) {
+ std::lock_guard<std::mutex> lock(slots_mtx_);
+ auto slot_it = slots_by_source_and_id_.find({source, id});
+ if (slot_it == slots_by_source_and_id_.end()) {
+ return;
+ }
+ slots_by_source_and_id_.erase(slot_it);
+ active_slots_[slot_it->second] = false;
+ }
+
+ size_t NumActiveSlots() {
+ std::lock_guard<std::mutex> lock(slots_mtx_);
+ return slots_by_source_and_id_.size();
+ }
+
+ void OnDisconnectedSource(void* source) {
+ std::lock_guard<std::mutex> lock(slots_mtx_);
+ auto it = slots_by_source_and_id_.begin();
+ while (it != slots_by_source_and_id_.end()) {
+ if (it->first.first == source) {
+ active_slots_[it->second] = false;
+ it = slots_by_source_and_id_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+
+ private:
+ int32_t UseNewSlot() {
+ // This is not the most efficient implementation for a large number of
+ // slots, but that case should be extremely rare. For the typical number of
+ // slots iterating over a vector is likely faster than using other data
+ // structures.
+ for (auto slot = 0; slot < active_slots_.size(); ++slot) {
+ if (!active_slots_[slot]) {
+ active_slots_[slot] = true;
+ return slot;
+ }
+ }
+ active_slots_.push_back(true);
+ return active_slots_.size() - 1;
+ }
+
+ std::unique_ptr<InputSocket> socket_;
+
+ std::mutex slots_mtx_;
+ std::map<std::pair<void*, int32_t>, int32_t> slots_by_source_and_id_;
+ std::vector<bool> active_slots_;
};
// Implements the InputConnector interface using unix socket based virtual input
@@ -138,9 +207,11 @@
public:
Result<void> SendTouchEvent(const std::string& device_label, int x, int y,
bool down) override;
- Result<void> SendMultiTouchEvent(const std::string& device_label,
+ Result<void> SendMultiTouchEvent(void* source,
+ const std::string& device_label,
const std::vector<MultitouchSlot>& slots,
bool down) override;
+ void OnDisconnectedSource(void* source) override;
Result<void> SendKeyboardEvent(uint16_t code, bool down) override;
Result<void> SendRotaryEvent(int pixels) override;
Result<void> SendSwitchesEvent(uint16_t code, bool state) override;
@@ -175,13 +246,13 @@
CF_EXPECT(ts_it != touch_devices_.end(),
"Unknown touch device: " << device_label);
auto& ts = ts_it->second;
- ts.socket->WriteEvents(std::move(buffer));
+ ts.WriteEvents(std::move(buffer));
return {};
}
Result<void> InputSocketsConnector::SendMultiTouchEvent(
- const std::string& device_label, const std::vector<MultitouchSlot>& slots,
- bool down) {
+ void* source, const std::string& device_label,
+ const std::vector<MultitouchSlot>& slots, bool down) {
auto buffer = CreateBuffer(event_type_, 1 + 7 * slots.size());
CF_EXPECT(buffer != nullptr, "Failed to allocate input events buffer");
@@ -191,40 +262,52 @@
auto& ts = ts_it->second;
for (auto& f : slots) {
- auto this_slot = f.slot;
auto this_id = f.id;
auto this_x = f.x;
auto this_y = f.y;
+ auto is_new_contact = !ts.HasSlot(source, this_id);
+ auto was_down = ts.NumActiveSlots() > 0;
+
+ // Make sure to call HasSlot before this line or it will always return true
+ auto this_slot = ts.GetOrAcquireSlot(source, this_id);
+
+ // BTN_TOUCH DOWN must be the first event in a series
+ if (down && !was_down) {
+ buffer->AddEvent(EV_KEY, BTN_TOUCH, 1);
+ }
+
buffer->AddEvent(EV_ABS, ABS_MT_SLOT, this_slot);
if (down) {
- bool is_new = ts.active_slots.insert(this_slot).second;
- if (is_new) {
+ if (is_new_contact) {
+ // We already assigned this slot to this source and id combination, we
+ // could use any tracking id for the slot as long as it's greater than 0
buffer->AddEvent(EV_ABS, ABS_MT_TRACKING_ID, this_id);
- if (ts.active_slots.size() == 1) {
- buffer->AddEvent(EV_KEY, BTN_TOUCH, 1);
- }
}
buffer->AddEvent(EV_ABS, ABS_MT_POSITION_X, this_x);
buffer->AddEvent(EV_ABS, ABS_MT_POSITION_Y, this_y);
- // send ABS_X and ABS_Y for single-touch compatibility
- buffer->AddEvent(EV_ABS, ABS_X, this_x);
- buffer->AddEvent(EV_ABS, ABS_Y, this_y);
} else {
// released touch
- buffer->AddEvent(EV_ABS, ABS_MT_TRACKING_ID, this_id);
- ts.active_slots.erase(this_slot);
- if (ts.active_slots.empty()) {
- buffer->AddEvent(EV_KEY, BTN_TOUCH, 0);
- }
+ buffer->AddEvent(EV_ABS, ABS_MT_TRACKING_ID, -1);
+ ts.ReleaseSlot(source, this_id);
+ }
+ // Send BTN_TOUCH UP when no more contacts are detected
+ if (was_down && ts.NumActiveSlots() == 0) {
+ buffer->AddEvent(EV_KEY, BTN_TOUCH, 0);
}
}
buffer->AddEvent(EV_SYN, SYN_REPORT, 0);
- ts.socket->WriteEvents(std::move(buffer));
+ ts.WriteEvents(std::move(buffer));
return {};
}
+void InputSocketsConnector::OnDisconnectedSource(void* source) {
+ for (auto& it: touch_devices_) {
+ it.second.OnDisconnectedSource(source);
+ }
+}
+
Result<void> InputSocketsConnector::SendKeyboardEvent(uint16_t code,
bool down) {
CF_EXPECT(keyboard_ != nullptr, "No keyboard device setup");
@@ -267,9 +350,8 @@
CHECK(connector_->touch_devices_.find(device_label) ==
connector_->touch_devices_.end())
<< "Multiple touch devices with same label: " << device_label;
- connector_->touch_devices_.emplace(
- device_label,
- TouchDevice{.socket = std::make_unique<InputSocket>(server)});
+ connector_->touch_devices_.emplace(device_label,
+ std::make_unique<InputSocket>(server));
}
void InputSocketsConnectorBuilder::WithKeyboard(SharedFD server) {
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index 5fbb5fe..6c27814 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -717,11 +717,30 @@
crosvm_cmd.AddHvcSink();
}
+
// /dev/hvc13 = sensors
crosvm_cmd.AddHvcReadWrite(
instance.PerInstanceInternalPath("sensors_fifo_vm.out"),
instance.PerInstanceInternalPath("sensors_fifo_vm.in"));
+ // /dev/hvc14 = MCU CONTROL
+ if (instance.mcu()["control"]["type"].asString() == "serial") {
+ auto path = instance.PerInstanceInternalPath("mcu");
+ path += "/" + instance.mcu()["control"]["path"].asString();
+ crosvm_cmd.AddHvcReadWrite(path, path);
+ } else {
+ crosvm_cmd.AddHvcSink();
+ }
+
+ // /dev/hvc15 = MCU UART
+ if (instance.mcu()["uart0"]["type"].asString() == "serial") {
+ auto path = instance.PerInstanceInternalPath("mcu");
+ path += "/" + instance.mcu()["uart0"]["path"].asString();
+ crosvm_cmd.AddHvcReadWrite(path, path);
+ } else {
+ crosvm_cmd.AddHvcSink();
+ }
+
for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
crosvm_cmd.AddHvcSink();
}
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index f5d8df2..4887d7e 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -315,6 +315,18 @@
".0,chardev=hvc", hvc_num);
hvc_num++;
};
+ auto add_hvc_serial = [&qemu_cmd, &hvc_num](const std::string& prefix) {
+ qemu_cmd.AddParameter("-chardev");
+ qemu_cmd.AddParameter("serial,id=hvc", hvc_num, ",path=", prefix);
+ qemu_cmd.AddParameter("-device");
+ qemu_cmd.AddParameter(
+ "virtio-serial-pci-non-transitional,max_ports=1,id=virtio-serial",
+ hvc_num, ",bus=hvc-bridge,addr=", fmt::format("{:0>2x}", hvc_num + 1));
+ qemu_cmd.AddParameter("-device");
+ qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
+ ".0,chardev=hvc", hvc_num);
+ hvc_num++;
+ };
bool is_arm = arch_ == Arch::Arm || arch_ == Arch::Arm64;
bool is_x86 = arch_ == Arch::X86 || arch_ == Arch::X86_64;
@@ -598,6 +610,24 @@
// /dev/hvc13 = sensors
add_hvc_sink();
+ // /dev/hvc14 = MCU CONTROL
+ if (instance.mcu()["control"]["type"].asString() == "serial") {
+ auto path = instance.PerInstanceInternalPath("mcu");
+ path += "/" + instance.mcu()["control"]["path"].asString();
+ add_hvc_serial(path);
+ } else {
+ add_hvc_sink();
+ }
+
+ // /dev/hvc15 = MCU UART
+ if (instance.mcu()["uart0"]["type"].asString() == "serial") {
+ auto path = instance.PerInstanceInternalPath("mcu");
+ path += "/" + instance.mcu()["uart0"]["path"].asString();
+ add_hvc_serial(path);
+ } else {
+ add_hvc_sink();
+ }
+
auto disk_num = instance.virtual_disk_paths().size();
for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
diff --git a/host/libs/vm_manager/vm_manager.h b/host/libs/vm_manager/vm_manager.h
index 11c5291..8509217 100644
--- a/host/libs/vm_manager/vm_manager.h
+++ b/host/libs/vm_manager/vm_manager.h
@@ -60,7 +60,9 @@
// - /dev/hvc11 = keymint
// - /dev/hvc12 = NFC
// - /dev/hvc13 = sensors
- static const int kDefaultNumHvcs = 14;
+ // - /dev/hvc14 = MCU control
+ // - /dev/hvc15 = MCU UART
+ static const int kDefaultNumHvcs = 16;
// This is the number of virtual disks (block devices) that should be
// configured by the VmManager. Related to the description above regarding
diff --git a/shared/bluetooth/device_vendor.mk b/shared/bluetooth/device_vendor.mk
index 82b460f..6d37109 100644
--- a/shared/bluetooth/device_vendor.mk
+++ b/shared/bluetooth/device_vendor.mk
@@ -40,6 +40,7 @@
PRODUCT_PACKAGES += \
android.hardware.bluetooth-service.default \
+ android.hardware.bluetooth.finder-service.default \
bt_vhci_forwarder
# Bluetooth initialization configuration is copied to the init folder here instead of being added
diff --git a/shared/config/ueventd.rc b/shared/config/ueventd.rc
index 4010624..5b01e36 100644
--- a/shared/config/ueventd.rc
+++ b/shared/config/ueventd.rc
@@ -50,5 +50,10 @@
# sensors
/dev/hvc13 0660 system system
+# MCU control
+/dev/hvc14 0666 system system
+# MCU UART
+/dev/hvc15 0666 system system
+
# Factory Reset Protection
/dev/block/by-name/frp 0660 system system
diff --git a/shared/sepolicy/vendor/file.te b/shared/sepolicy/vendor/file.te
index 0762340..c6d5ae6 100644
--- a/shared/sepolicy/vendor/file.te
+++ b/shared/sepolicy/vendor/file.te
@@ -1,3 +1,5 @@
# File types
type sysfs_iio_devices, fs_type, sysfs_type;
type mediadrm_vendor_data_file, file_type, data_file_type;
+type mcu_control_device, dev_type;
+type mcu_uart_device, dev_type;
diff --git a/shared/sepolicy/vendor/file_contexts b/shared/sepolicy/vendor/file_contexts
index 45e48dc..4913b70 100644
--- a/shared/sepolicy/vendor/file_contexts
+++ b/shared/sepolicy/vendor/file_contexts
@@ -41,6 +41,11 @@
# hvc13 for Sensors
/dev/hvc13 u:object_r:sensors_device:s0
+# hvc14 for MCU control
+/dev/hvc14 u:object_r:mcu_control_device:s0
+# hvc14 for MCU UART
+/dev/hvc15 u:object_r:mcu_uart_device:s0
+
# ARM serial console device
/dev/ttyAMA[0-9]* u:object_r:serial_device:s0
diff --git a/shared/sepolicy/vendor/init.te b/shared/sepolicy/vendor/init.te
index 1061f0d..203bf5f 100644
--- a/shared/sepolicy/vendor/init.te
+++ b/shared/sepolicy/vendor/init.te
@@ -33,5 +33,7 @@
allow init oemlock_device:chr_file rw_file_perms;
allow init keymint_device:chr_file rw_file_perms;
allow init sensors_device:chr_file rw_file_perms;
+allow init mcu_control_device:chr_file rw_file_perms;
+allow init mcu_uart_device:chr_file rw_file_perms;
allow init frp_block_device:blk_file setattr;
diff --git a/shared/sepolicy/vendor/ueventd.te b/shared/sepolicy/vendor/ueventd.te
index 749e8bd..63d279c 100644
--- a/shared/sepolicy/vendor/ueventd.te
+++ b/shared/sepolicy/vendor/ueventd.te
@@ -7,3 +7,5 @@
allow ueventd oemlock_device:chr_file { rw_file_perms create setattr };
allow ueventd keymint_device:chr_file { rw_file_perms create setattr };
allow ueventd sensors_device:chr_file { rw_file_perms create setattr };
+allow ueventd mcu_control_device:chr_file { rw_file_perms create setattr };
+allow ueventd mcu_uart_device:chr_file { rw_file_perms create setattr };