Merge "Enable fastbootd on cuttlefish"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 8c7bc4b..5666f32 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -2,6 +2,9 @@
"postsubmit" : [
{
"name": "tombstone_transmit_tests"
+ },
+ {
+ "name": "RebootRecoveryTest"
}
],
"presubmit": [
diff --git a/build/Android.bp b/build/Android.bp
index e882b70..c99ce20 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -12,6 +12,18 @@
pluginFor: ["soong_build"],
}
+// Allow cvd-host-package.go to read custom action config variables
+// from ctx.Config().VendorConfig("cvd")
+soong_config_module_type {
+ name: "cvd_host_package_customization",
+ module_type: "cvd_host_package",
+ config_namespace: "cvd",
+ value_variables: [
+ "custom_action_config",
+ "custom_action_servers",
+ ],
+}
+
cvd_host_tools = [
"android.hardware.automotive.vehicle@2.0-virtualization-grpc-server",
"adb",
@@ -130,7 +142,7 @@
"x86_64_linux_gnu_libOpenglRender.so_for_crosvm",
]
-cvd_host_package {
+cvd_host_package_customization {
name: "cvd-host_package",
deps: cvd_host_tools +
cvd_host_tests,
diff --git a/build/README.md b/build/README.md
new file mode 100644
index 0000000..dc66c0a
--- /dev/null
+++ b/build/README.md
@@ -0,0 +1,27 @@
+## Custom Actions
+
+To add custom actions to the WebRTC control panel, create a custom action config
+JSON file in your virtual device product makefile directory, create a
+`prebuilt_etc_host` module for the JSON file with `sub_dir`
+`cvd_custom_action_config`, then set the build variable
+`SOONG_CONFIG_cvd_custom_action_config` to the name of that module. For example:
+
+```
+Android.bp:
+ prebuilt_etc_host {
+ name: "my_custom_action_config.json",
+ src: "my_custom_action_config.json",
+ // The sub_dir must always equal the following value:
+ sub_dir: "cvd_custom_action_config",
+ }
+
+my_virtual_device.mk:
+ SOONG_CONFIG_NAMESPACES += cvd
+ SOONG_CONFIG_cvd += custom_action_config
+ SOONG_CONFIG_cvd_custom_action_config := my_custom_action_config.json
+```
+
+TODO(b/171709037): Add documentation to source.android.com
+
+See https://source.android.com/setup/create/cuttlefish-control-panel for
+detailed information about the format of the config file.
diff --git a/build/cvd-host-package.go b/build/cvd-host-package.go
index 67e218d..53202c2 100644
--- a/build/cvd-host-package.go
+++ b/build/cvd-host-package.go
@@ -16,6 +16,7 @@
import (
"fmt"
+ "strings"
"github.com/google/blueprint"
@@ -56,6 +57,7 @@
ctx.AddVariationDependencies(nil, cvdHostPackageDependencyTag, dep)
}
}
+
variations := []blueprint.Variation{
{Mutator: "os", Variation: ctx.Target().Os.String()},
{Mutator: "arch", Variation: android.Common.String()},
@@ -65,6 +67,20 @@
ctx.AddFarVariationDependencies(variations, cvdHostPackageDependencyTag, dep)
}
}
+
+ // If cvd_custom_action_config is set, include custom action servers in the
+ // host package as specified by cvd_custom_action_servers.
+ customActionConfig := ctx.Config().VendorConfig("cvd").String("custom_action_config")
+ if customActionConfig != "" && ctx.OtherModuleExists(customActionConfig) {
+ ctx.AddVariationDependencies(variations, cvdHostPackageDependencyTag,
+ customActionConfig)
+ for _, dep := range strings.Split(
+ ctx.Config().VendorConfig("cvd").String("custom_action_servers"), " ") {
+ if ctx.OtherModuleExists(dep) {
+ ctx.AddVariationDependencies(nil, cvdHostPackageDependencyTag, dep)
+ }
+ }
+ }
}
var pctx = android.NewPackageContext("android/soong/cuttlefish")
diff --git a/common/libs/utils/subprocess.h b/common/libs/utils/subprocess.h
index d9a571e..413444f 100644
--- a/common/libs/utils/subprocess.h
+++ b/common/libs/utils/subprocess.h
@@ -23,6 +23,8 @@
#include <string>
#include <vector>
+#include <android-base/logging.h>
+
#include <common/libs/fs/shared_fd.h>
namespace cuttlefish {
@@ -183,6 +185,21 @@
}
return false;
}
+ // Similar to AddParameter, except the args are appended to the last (most
+ // recently-added) parameter in the command.
+ template <typename... Args>
+ bool AppendToLastParameter(Args... args) {
+ if (command_.empty()) {
+ LOG(ERROR) << "There is no parameter to append to.";
+ return false;
+ }
+ std::stringstream ss;
+ if (BuildParameter(&ss, args...)) {
+ command_[command_.size()-1] += ss.str();
+ return true;
+ }
+ return false;
+ }
ParameterBuilder GetParameterBuilder() { return ParameterBuilder(this); }
diff --git a/guest/hals/hwcomposer/vsocket_screen_view.cpp b/guest/hals/hwcomposer/vsocket_screen_view.cpp
index 7fbe0ba..0960688 100644
--- a/guest/hals/hwcomposer/vsocket_screen_view.cpp
+++ b/guest/hals/hwcomposer/vsocket_screen_view.cpp
@@ -91,13 +91,19 @@
"Compositions will occur, but frames won't be sent anywhere");
return;
}
- int current_seq = 0;
+ // The client detector thread needs to be started after the connection to the
+ // socket has been made
+ client_detector_thread_ = std::thread([this]() { ClientDetectorLoop(); });
+
+ unsigned int current_seq = 0;
+ unsigned int last_sent_seq = 0;
int current_offset;
ALOGI("Broadcaster thread loop starting");
while (true) {
{
std::unique_lock<std::mutex> lock(mutex_);
- while (running_ && current_seq == current_seq_) {
+ while (running_ && current_seq == current_seq_ &&
+ (!send_frames_ || last_sent_seq == current_seq)) {
cond_var_.wait(lock);
}
if (!running_) {
@@ -107,17 +113,50 @@
current_offset = current_offset_;
current_seq = current_seq_;
}
- int32_t size = buffer_size();
- screen_server_->Write(&size, sizeof(size));
- auto buff = static_cast<char*>(GetBuffer(current_offset));
- while (size > 0) {
- auto written = screen_server_->Write(buff, size);
- if (written == -1) {
- ALOGE("Broadcaster thread failed to write frame: %s", strerror(errno));
+ if (send_frames_ && last_sent_seq != current_seq) {
+ last_sent_seq = current_seq;
+ if (!SendFrame(current_offset)) {
break;
}
- size -= written;
- buff += written;
+ }
+ }
+}
+
+bool VsocketScreenView::SendFrame(int offset) {
+ int32_t size = buffer_size();
+ screen_server_->Write(&size, sizeof(size));
+ auto buff = static_cast<char*>(GetBuffer(offset));
+ while (size > 0) {
+ auto written = screen_server_->Write(buff, size);
+ if (written == -1) {
+ ALOGE("Broadcaster thread failed to write frame: %s",
+ screen_server_->StrError());
+ return false;
+ }
+ size -= written;
+ buff += written;
+ }
+ return true;
+}
+
+void VsocketScreenView::ClientDetectorLoop() {
+ char buffer[8];
+ while (running_) {
+ auto read = screen_server_->Read(buffer, sizeof(buffer));
+ if (read == -1) {
+ ALOGE("Client detector thread failed to read from screen server: %s",
+ screen_server_->StrError());
+ break;
+ }
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ // The last byte sent by the server indicates the presence of clients.
+ send_frames_ = read > 0 && buffer[read - 1];
+ cond_var_.notify_all();
+ }
+ if (read == 0) {
+ ALOGE("screen server closed!");
+ break;
}
}
}
diff --git a/guest/hals/hwcomposer/vsocket_screen_view.h b/guest/hals/hwcomposer/vsocket_screen_view.h
index edd41c9..cb4f784 100644
--- a/guest/hals/hwcomposer/vsocket_screen_view.h
+++ b/guest/hals/hwcomposer/vsocket_screen_view.h
@@ -46,12 +46,15 @@
bool ConnectToScreenServer();
void GetScreenParameters();
void BroadcastLoop();
+ void ClientDetectorLoop();
+ bool SendFrame(int offset);
std::vector<char> inner_buffer_;
cuttlefish::SharedFD screen_server_;
std::thread broadcast_thread_;
+ std::thread client_detector_thread_;
int current_offset_ = 0;
- int current_seq_ = 0;
+ unsigned int current_seq_ = 0;
std::mutex mutex_;
std::condition_variable cond_var_;
bool running_ = true;
@@ -59,6 +62,7 @@
int32_t y_res_{1280};
int32_t dpi_{160};
int32_t refresh_rate_{60};
+ bool send_frames_{false};
};
} // namespace cuttlefish
diff --git a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/ConnectivityChecker.java b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/ConnectivityChecker.java
index 84998ee..43f4e3b 100644
--- a/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/ConnectivityChecker.java
+++ b/guest/monitoring/cuttlefish_service/java/com/android/google/gce/gceservice/ConnectivityChecker.java
@@ -28,12 +28,15 @@
private static final String LOG_TAG = "GceConnChecker";
private static final String MOBILE_NETWORK_CONNECTED_MESSAGE =
"VIRTUAL_DEVICE_NETWORK_MOBILE_CONNECTED";
+ private static final String ETHERNET_NETWORK_CONNECTED_MESSAGE =
+ "VIRTUAL_DEVICE_NETWORK_ETHERNET_CONNECTED";
private final Context mContext;
private final EventReporter mEventReporter;
private final GceFuture<Boolean> mConnected = new GceFuture<Boolean>("Connectivity");
// TODO(schuffelen): Figure out why this has to be static in order to not report 3 times.
private static boolean reportedMobileConnectivity = false;
+ private static boolean reportedEthernetConnectivity = false;
public ConnectivityChecker(Context context, EventReporter eventReporter) {
super(LOG_TAG);
@@ -54,11 +57,16 @@
NetworkInfo info = connManager.getNetworkInfo(network);
if (info.isConnected()) {
NetworkCapabilities capabilities = connManager.getNetworkCapabilities(network);
- if (capabilities != null
- && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
- && !reportedMobileConnectivity) {
- mEventReporter.reportMessage(MOBILE_NETWORK_CONNECTED_MESSAGE);
- reportedMobileConnectivity = true;
+ if (capabilities != null) {
+ if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && !reportedMobileConnectivity) {
+ mEventReporter.reportMessage(MOBILE_NETWORK_CONNECTED_MESSAGE);
+ reportedMobileConnectivity = true;
+ } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
+ && !reportedEthernetConnectivity) {
+ mEventReporter.reportMessage(ETHERNET_NETWORK_CONNECTED_MESSAGE);
+ reportedEthernetConnectivity = true;
+ }
}
}
}
diff --git a/host/commands/assemble_cvd/Android.bp b/host/commands/assemble_cvd/Android.bp
index 0f78b2a..2bbbf06 100644
--- a/host/commands/assemble_cvd/Android.bp
+++ b/host/commands/assemble_cvd/Android.bp
@@ -31,10 +31,12 @@
cc_binary {
name: "assemble_cvd",
srcs: [
+ "alloc.cc",
"assemble_cvd.cc",
"boot_config.cc",
"boot_image_unpacker.cc",
"boot_image_utils.cc",
+ "clean.cc",
"disk_flags.cc",
"flags.cc",
"image_aggregator.cc",
diff --git a/host/commands/assemble_cvd/alloc.cc b/host/commands/assemble_cvd/alloc.cc
new file mode 100644
index 0000000..493de55
--- /dev/null
+++ b/host/commands/assemble_cvd/alloc.cc
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/commands/assemble_cvd/alloc.h"
+
+#include <iomanip>
+#include <sstream>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/commands/assemble_cvd/assembler_defs.h"
+#include "host/libs/allocd/request.h"
+#include "host/libs/allocd/utils.h"
+
+static std::string StrForInstance(const std::string& prefix, int num) {
+ std::ostringstream stream;
+ stream << prefix << std::setfill('0') << std::setw(2) << num;
+ return stream.str();
+}
+
+IfaceConfig DefaultNetworkInterfaces(int num) {
+ IfaceConfig config{};
+ config.mobile_tap.name = StrForInstance("cvd-mtap-", num);
+ config.mobile_tap.resource_id = 0;
+ config.mobile_tap.session_id = 0;
+
+ config.wireless_tap.name = StrForInstance("cvd-wtap-", num);
+ config.wireless_tap.resource_id = 0;
+ config.wireless_tap.session_id = 0;
+
+ config.ethernet_tap.name = StrForInstance("cvd-etap-", num);
+ config.ethernet_tap.resource_id = 0;
+ config.ethernet_tap.session_id = 0;
+
+ return config;
+}
+
+std::optional<IfaceConfig> AllocateNetworkInterfaces() {
+ IfaceConfig config{};
+
+ cuttlefish::SharedFD allocd_sock = cuttlefish::SharedFD::SocketLocalClient(
+ cuttlefish::kDefaultLocation, false, SOCK_STREAM);
+ if (!allocd_sock->IsOpen()) {
+ LOG(FATAL) << "Unable to connect to allocd on "
+ << cuttlefish::kDefaultLocation << ": "
+ << allocd_sock->StrError();
+ exit(cuttlefish::kAllocdConnectionError);
+ }
+
+ Json::Value resource_config;
+ Json::Value request_list;
+ Json::Value req;
+ req["request_type"] = "create_interface";
+ req["uid"] = geteuid();
+ req["iface_type"] = "mtap";
+ request_list.append(req);
+ req["iface_type"] = "wtap";
+ request_list.append(req);
+ req["iface_type"] = "etap";
+ request_list.append(req);
+
+ resource_config["config_request"]["request_list"] = request_list;
+
+ if (!cuttlefish::SendJsonMsg(allocd_sock, resource_config)) {
+ LOG(FATAL) << "Failed to send JSON to allocd\n";
+ return std::nullopt;
+ }
+
+ auto resp_opt = cuttlefish::RecvJsonMsg(allocd_sock);
+ if (!resp_opt.has_value()) {
+ LOG(FATAL) << "Bad Response from allocd\n";
+ exit(cuttlefish::kAllocdConnectionError);
+ }
+ auto resp = resp_opt.value();
+
+ if (!resp.isMember("config_status") || !resp["config_status"].isString()) {
+ LOG(FATAL) << "Bad response from allocd: " << resp;
+ exit(cuttlefish::kAllocdConnectionError);
+ }
+
+ if (resp["config_status"].asString() !=
+ cuttlefish::StatusToStr(cuttlefish::RequestStatus::Success)) {
+ LOG(FATAL) << "Failed to allocate interfaces " << resp;
+ exit(cuttlefish::kAllocdConnectionError);
+ }
+
+ if (!resp.isMember("session_id") || !resp["session_id"].isUInt()) {
+ LOG(FATAL) << "Bad response from allocd: " << resp;
+ exit(cuttlefish::kAllocdConnectionError);
+ }
+ auto session_id = resp["session_id"].asUInt();
+
+ if (!resp.isMember("response_list") || !resp["response_list"].isArray()) {
+ LOG(FATAL) << "Bad response from allocd: " << resp;
+ exit(cuttlefish::kAllocdConnectionError);
+ }
+
+ Json::Value resp_list = resp["response_list"];
+ Json::Value mtap_resp;
+ Json::Value wtap_resp;
+ Json::Value etap_resp;
+ for (Json::Value::ArrayIndex i = 0; i != resp_list.size(); ++i) {
+ auto ty = cuttlefish::StrToIfaceTy(resp_list[i]["iface_type"].asString());
+
+ switch (ty) {
+ case cuttlefish::IfaceType::mtap: {
+ mtap_resp = resp_list[i];
+ break;
+ }
+ case cuttlefish::IfaceType::wtap: {
+ wtap_resp = resp_list[i];
+ break;
+ }
+ case cuttlefish::IfaceType::etap: {
+ etap_resp = resp_list[i];
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+
+ if (!mtap_resp.isMember("iface_type")) {
+ LOG(ERROR) << "Missing mtap response from allocd";
+ return std::nullopt;
+ }
+ if (!wtap_resp.isMember("iface_type")) {
+ LOG(ERROR) << "Missing wtap response from allocd";
+ return std::nullopt;
+ }
+ if (!etap_resp.isMember("iface_type")) {
+ LOG(ERROR) << "Missing etap response from allocd";
+ return std::nullopt;
+ }
+
+ config.mobile_tap.name = mtap_resp["iface_name"].asString();
+ config.mobile_tap.resource_id = mtap_resp["resource_id"].asUInt();
+ config.mobile_tap.session_id = session_id;
+
+ config.wireless_tap.name = wtap_resp["iface_name"].asString();
+ config.wireless_tap.resource_id = wtap_resp["resource_id"].asUInt();
+ config.wireless_tap.session_id = session_id;
+
+ config.ethernet_tap.name = etap_resp["iface_name"].asString();
+ config.ethernet_tap.resource_id = etap_resp["resource_id"].asUInt();
+ config.ethernet_tap.session_id = session_id;
+
+ return config;
+}
+
diff --git a/host/commands/assemble_cvd/alloc.h b/host/commands/assemble_cvd/alloc.h
new file mode 100644
index 0000000..0de8795
--- /dev/null
+++ b/host/commands/assemble_cvd/alloc.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <stdint.h>
+#include <optional>
+#include <string>
+
+struct IfaceData {
+ std::string name;
+ uint32_t session_id;
+ uint32_t resource_id;
+};
+
+struct IfaceConfig {
+ IfaceData mobile_tap;
+ IfaceData wireless_tap;
+ IfaceData ethernet_tap;
+};
+
+IfaceConfig DefaultNetworkInterfaces(int num);
+
+// Acquires interfaces from the resource allocator daemon.
+std::optional<IfaceConfig> AllocateNetworkInterfaces();
diff --git a/host/commands/assemble_cvd/clean.cc b/host/commands/assemble_cvd/clean.cc
new file mode 100644
index 0000000..add0e87
--- /dev/null
+++ b/host/commands/assemble_cvd/clean.cc
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/commands/assemble_cvd/clean.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include <regex>
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "host/commands/assemble_cvd/flags.h"
+#include "common/libs/utils/files.h"
+
+namespace {
+
+bool CleanPriorFiles(const std::string& path, const std::set<std::string>& preserving) {
+ if (preserving.count(cuttlefish::cpp_basename(path))) {
+ LOG(DEBUG) << "Preserving: " << path;
+ return true;
+ }
+ struct stat statbuf;
+ if (lstat(path.c_str(), &statbuf) < 0) {
+ int error_num = errno;
+ if (error_num == ENOENT) {
+ return true;
+ } else {
+ LOG(ERROR) << "Could not stat \"" << path << "\": " << strerror(error_num);
+ return false;
+ }
+ }
+ if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
+ LOG(DEBUG) << "Deleting: " << path;
+ if (unlink(path.c_str()) < 0) {
+ int error_num = errno;
+ LOG(ERROR) << "Could not unlink \"" << path << "\", error was " << strerror(error_num);
+ return false;
+ }
+ return true;
+ }
+ std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(path.c_str()), closedir);
+ if (!dir) {
+ int error_num = errno;
+ LOG(ERROR) << "Could not clean \"" << path << "\": error was " << strerror(error_num);
+ return false;
+ }
+ for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
+ std::string entity_name(entity->d_name);
+ if (entity_name == "." || entity_name == "..") {
+ continue;
+ }
+ std::string entity_path = path + "/" + entity_name;
+ if (!CleanPriorFiles(entity_path.c_str(), preserving)) {
+ return false;
+ }
+ }
+ if (rmdir(path.c_str()) < 0) {
+ if (!(errno == EEXIST || errno == ENOTEMPTY)) {
+ // If EEXIST or ENOTEMPTY, probably because a file was preserved
+ int error_num = errno;
+ LOG(ERROR) << "Could not rmdir \"" << path << "\", error was " << strerror(error_num);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool CleanPriorFiles(const std::vector<std::string>& paths, const std::set<std::string>& preserving) {
+ std::string prior_files;
+ for (auto path : paths) {
+ struct stat statbuf;
+ if (stat(path.c_str(), &statbuf) < 0 && errno != ENOENT) {
+ // If ENOENT, it doesn't exist yet, so there is no work to do'
+ int error_num = errno;
+ LOG(ERROR) << "Could not stat \"" << path << "\": " << strerror(error_num);
+ return false;
+ }
+ bool is_directory = (statbuf.st_mode & S_IFMT) == S_IFDIR;
+ prior_files += (is_directory ? (path + "/*") : path) + " ";
+ }
+ LOG(DEBUG) << "Assuming prior files of " << prior_files;
+ std::string lsof_cmd = "lsof -t " + prior_files + " >/dev/null 2>&1";
+ int rval = std::system(lsof_cmd.c_str());
+ // lsof returns 0 if any of the files are open
+ if (WEXITSTATUS(rval) == 0) {
+ LOG(ERROR) << "Clean aborted: files are in use";
+ return false;
+ }
+ for (const auto& path : paths) {
+ if (!CleanPriorFiles(path, preserving)) {
+ LOG(ERROR) << "Remove of file under \"" << path << "\" failed";
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+bool CleanPriorFiles(
+ const std::set<std::string>& preserving,
+ const std::string& assembly_dir,
+ const std::string& instance_dir) {
+ std::vector<std::string> paths = {
+ // Everything in the assembly directory
+ assembly_dir,
+ // The environment file
+ GetCuttlefishEnvPath(),
+ // The global link to the config file
+ cuttlefish::GetGlobalConfigFileLink(),
+ };
+
+ std::string runtime_dir_parent =
+ cuttlefish::cpp_dirname(cuttlefish::AbsolutePath(instance_dir));
+ std::string runtime_dirs_basename =
+ cuttlefish::cpp_basename(cuttlefish::AbsolutePath(instance_dir));
+
+ std::regex instance_dir_regex("^.+\\.[1-9]\\d*$");
+ for (const auto& path : cuttlefish::DirectoryContents(runtime_dir_parent)) {
+ std::string absl_path = runtime_dir_parent + "/" + path;
+ if((path.rfind(runtime_dirs_basename, 0) == 0) && std::regex_match(path, instance_dir_regex) &&
+ cuttlefish::DirectoryExists(absl_path)) {
+ paths.push_back(absl_path);
+ }
+ }
+ paths.push_back(instance_dir);
+ return CleanPriorFiles(paths, preserving);
+}
diff --git a/host/commands/assemble_cvd/clean.h b/host/commands/assemble_cvd/clean.h
new file mode 100644
index 0000000..e24080a
--- /dev/null
+++ b/host/commands/assemble_cvd/clean.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <set>
+#include <string>
+
+bool CleanPriorFiles(
+ const std::set<std::string>& preserving,
+ const std::string& assembly_dir,
+ const std::string& instance_dir);
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index 7571817..511b6f0 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -23,13 +23,13 @@
#include "common/libs/utils/environment.h"
#include "common/libs/utils/files.h"
#include "common/libs/utils/tee_logging.h"
+#include "host/commands/assemble_cvd/alloc.h"
#include "host/commands/assemble_cvd/assembler_defs.h"
#include "host/commands/assemble_cvd/boot_config.h"
#include "host/commands/assemble_cvd/boot_image_unpacker.h"
+#include "host/commands/assemble_cvd/clean.h"
#include "host/commands/assemble_cvd/disk_flags.h"
#include "host/commands/assemble_cvd/image_aggregator.h"
-#include "host/libs/allocd/request.h"
-#include "host/libs/allocd/utils.h"
#include "host/libs/config/data_image.h"
#include "host/libs/config/fetcher_config.h"
#include "host/libs/config/host_tools_version.h"
@@ -238,6 +238,10 @@
DEFINE_bool(restart_subprocesses, true, "Restart any crashed host process");
DEFINE_bool(enable_vehicle_hal_grpc_server, true, "Enables the vehicle HAL "
"emulation gRPC server on the host");
+DEFINE_string(custom_action_config, "",
+ "Path to a custom action config JSON. Defaults to the file provided by "
+ "build variable CVD_CUSTOM_ACTION_CONFIG. If this build variable "
+ "is empty then the custom action config will be empty as well.");
DEFINE_bool(use_bootloader, true, "Boots the device using a bootloader");
DEFINE_string(bootloader, "", "Bootloader binary path");
DEFINE_string(boot_slot, "", "Force booting into the given slot. If empty, "
@@ -280,6 +284,8 @@
"the vsock cid of the i th instance would be C + i where i is in [1, N]"
"If --num_instances is not given, the default value of N is used.");
+DEFINE_bool(ethernet, false, "Enable Ethernet network interface");
+
DECLARE_string(system_image_dir);
namespace {
@@ -301,10 +307,6 @@
return port_range;
}
-std::string GetCuttlefishEnvPath() {
- return cuttlefish::StringFromEnv("HOME", ".") + "/.cuttlefish.sh";
-}
-
std::string GetLegacyConfigFilePath(const cuttlefish::CuttlefishConfig& config) {
return config.ForDefaultInstance().PerInstancePath("cuttlefish_config.json");
}
@@ -545,6 +547,44 @@
tmp_config_obj.set_vehicle_hal_grpc_server_binary(
cuttlefish::DefaultHostArtifactsPath("bin/android.hardware.automotive.vehicle@2.0-virtualization-grpc-server"));
+ std::string custom_action_config;
+ if (!FLAGS_custom_action_config.empty()) {
+ custom_action_config = FLAGS_custom_action_config;
+ } else {
+ std::string custom_action_config_dir =
+ cuttlefish::DefaultHostArtifactsPath("etc/cvd_custom_action_config");
+ if (cuttlefish::DirectoryExists(custom_action_config_dir)) {
+ auto custom_action_configs = cuttlefish::DirectoryContents(
+ custom_action_config_dir);
+ // Two entries are always . and ..
+ if (custom_action_configs.size() > 3) {
+ LOG(ERROR) << "Expected at most one custom action config in "
+ << custom_action_config_dir << ". Please delete extras.";
+ } else if (custom_action_configs.size() == 3) {
+ for (const auto& config : custom_action_configs) {
+ if (android::base::EndsWithIgnoreCase(config, ".json")) {
+ custom_action_config = custom_action_config_dir + "/" + config;
+ }
+ }
+ }
+ }
+ }
+ // Load the custom action config JSON.
+ if (custom_action_config != "") {
+ Json::Reader reader;
+ std::ifstream ifs(custom_action_config);
+ Json::Value dictionary;
+ if (!reader.parse(ifs, dictionary)) {
+ LOG(ERROR) << "Could not read custom actions config file " << custom_action_config
+ << ": " << reader.getFormattedErrorMessages();
+ }
+ std::vector<cuttlefish::CustomActionConfig> custom_actions;
+ for (Json::Value custom_action : dictionary) {
+ custom_actions.push_back(cuttlefish::CustomActionConfig(custom_action));
+ }
+ tmp_config_obj.set_custom_actions(custom_actions);
+ }
+
tmp_config_obj.set_use_bootloader(FLAGS_use_bootloader);
tmp_config_obj.set_bootloader(FLAGS_bootloader);
@@ -562,6 +602,8 @@
tmp_config_obj.set_vhost_net(FLAGS_vhost_net);
+ tmp_config_obj.set_ethernet(FLAGS_ethernet);
+
std::vector<int> num_instances;
for (int i = 0; i < FLAGS_num_instances; i++) {
num_instances.push_back(cuttlefish::GetInstance() + i);
@@ -569,12 +611,17 @@
bool is_first_instance = true;
for (const auto& num : num_instances) {
- auto iface_opt = AcquireIfaces(num);
- if (!iface_opt.has_value()) {
- LOG(FATAL) << "Failed to acquire network interfaces";
+ IfaceConfig iface_config;
+ if (FLAGS_use_allocd) {
+ auto iface_opt = AllocateNetworkInterfaces();
+ if (!iface_opt.has_value()) {
+ LOG(FATAL) << "Failed to acquire network interfaces";
+ }
+ iface_config = iface_opt.value();
+ } else {
+ iface_config = DefaultNetworkInterfaces(num);
}
- auto iface_config = iface_opt.value();
auto instance = tmp_config_obj.ForInstance(num);
auto const_instance =
const_cast<const cuttlefish::CuttlefishConfig&>(tmp_config_obj)
@@ -593,8 +640,8 @@
instance.set_mobile_bridge_name(StrForInstance("cvd-mbr-", num));
instance.set_mobile_tap_name(iface_config.mobile_tap.name);
-
instance.set_wifi_tap_name(iface_config.wireless_tap.name);
+ instance.set_ethernet_tap_name(iface_config.ethernet_tap.name);
instance.set_vsock_guest_cid(FLAGS_vsock_guest_cid + num - cuttlefish::GetInstance());
@@ -711,6 +758,25 @@
google::FlagSettingMode::SET_FLAGS_DEFAULT);
}
+bool EnsureDirectoryExists(const std::string& directory_path) {
+ if (!cuttlefish::DirectoryExists(directory_path)) {
+ LOG(DEBUG) << "Setting up " << directory_path;
+ if (mkdir(directory_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
+ && errno != EEXIST) {
+ PLOG(ERROR) << "Failed to create dir: \"" << directory_path << "\" ";
+ return false;
+ }
+ }
+ return true;
+}
+
+void EnsureDirectoryExistsOrExit(
+ const std::string& directory_path, AssemblerExitCodes exit_code) {
+ if (!EnsureDirectoryExists(directory_path)) {
+ exit((int) exit_code);
+ }
+}
+
void SetDefaultFlagsForCrosvm() {
if (NumStreamers() == 0) {
// This makes WebRTC the default streamer unless the user requests
@@ -724,16 +790,15 @@
bool default_enable_sandbox = false;
std::set<const std::string> supported_archs{std::string("x86_64")};
if (supported_archs.find(cuttlefish::HostArch()) != supported_archs.end()) {
- default_enable_sandbox =
- [](const std::string& var_empty) -> bool {
- if (cuttlefish::DirectoryExists(var_empty)) {
- return cuttlefish::IsDirectoryEmpty(var_empty);
- }
- if (cuttlefish::FileExists(var_empty)) {
- return false;
- }
- return (::mkdir(var_empty.c_str(), 0755) == 0);
- }(cuttlefish::kCrosvmVarEmptyDir);
+ if (cuttlefish::DirectoryExists(cuttlefish::kCrosvmVarEmptyDir)) {
+ default_enable_sandbox =
+ cuttlefish::IsDirectoryEmpty(cuttlefish::kCrosvmVarEmptyDir);
+ } else if (cuttlefish::FileExists(cuttlefish::kCrosvmVarEmptyDir)) {
+ default_enable_sandbox = false;
+ } else {
+ default_enable_sandbox =
+ EnsureDirectoryExists(cuttlefish::kCrosvmVarEmptyDir);
+ }
}
SetCommandLineOptionWithMode("enable_sandbox",
@@ -789,114 +854,6 @@
return ResolveInstanceFiles();
}
-bool CleanPriorFiles(const std::string& path, const std::set<std::string>& preserving) {
- if (preserving.count(cuttlefish::cpp_basename(path))) {
- LOG(DEBUG) << "Preserving: " << path;
- return true;
- }
- struct stat statbuf;
- if (lstat(path.c_str(), &statbuf) < 0) {
- int error_num = errno;
- if (error_num == ENOENT) {
- return true;
- } else {
- LOG(ERROR) << "Could not stat \"" << path << "\": " << strerror(error_num);
- return false;
- }
- }
- if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
- LOG(DEBUG) << "Deleting: " << path;
- if (unlink(path.c_str()) < 0) {
- int error_num = errno;
- LOG(ERROR) << "Could not unlink \"" << path << "\", error was " << strerror(error_num);
- return false;
- }
- return true;
- }
- std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(path.c_str()), closedir);
- if (!dir) {
- int error_num = errno;
- LOG(ERROR) << "Could not clean \"" << path << "\": error was " << strerror(error_num);
- return false;
- }
- for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
- std::string entity_name(entity->d_name);
- if (entity_name == "." || entity_name == "..") {
- continue;
- }
- std::string entity_path = path + "/" + entity_name;
- if (!CleanPriorFiles(entity_path.c_str(), preserving)) {
- return false;
- }
- }
- if (rmdir(path.c_str()) < 0) {
- if (!(errno == EEXIST || errno == ENOTEMPTY)) {
- // If EEXIST or ENOTEMPTY, probably because a file was preserved
- int error_num = errno;
- LOG(ERROR) << "Could not rmdir \"" << path << "\", error was " << strerror(error_num);
- return false;
- }
- }
- return true;
-}
-
-bool CleanPriorFiles(const std::vector<std::string>& paths, const std::set<std::string>& preserving) {
- std::string prior_files;
- for (auto path : paths) {
- struct stat statbuf;
- if (stat(path.c_str(), &statbuf) < 0 && errno != ENOENT) {
- // If ENOENT, it doesn't exist yet, so there is no work to do'
- int error_num = errno;
- LOG(ERROR) << "Could not stat \"" << path << "\": " << strerror(error_num);
- return false;
- }
- bool is_directory = (statbuf.st_mode & S_IFMT) == S_IFDIR;
- prior_files += (is_directory ? (path + "/*") : path) + " ";
- }
- LOG(DEBUG) << "Assuming prior files of " << prior_files;
- std::string lsof_cmd = "lsof -t " + prior_files + " >/dev/null 2>&1";
- int rval = std::system(lsof_cmd.c_str());
- // lsof returns 0 if any of the files are open
- if (WEXITSTATUS(rval) == 0) {
- LOG(ERROR) << "Clean aborted: files are in use";
- return false;
- }
- for (const auto& path : paths) {
- if (!CleanPriorFiles(path, preserving)) {
- LOG(ERROR) << "Remove of file under \"" << path << "\" failed";
- return false;
- }
- }
- return true;
-}
-
-bool CleanPriorFiles(const std::set<std::string>& preserving) {
- std::vector<std::string> paths = {
- // Everything in the assembly directory
- FLAGS_assembly_dir,
- // The environment file
- GetCuttlefishEnvPath(),
- // The global link to the config file
- cuttlefish::GetGlobalConfigFileLink(),
- };
-
- std::string runtime_dir_parent =
- cuttlefish::cpp_dirname(cuttlefish::AbsolutePath(FLAGS_instance_dir));
- std::string runtime_dirs_basename =
- cuttlefish::cpp_basename(cuttlefish::AbsolutePath(FLAGS_instance_dir));
-
- std::regex instance_dir_regex("^.+\\.[1-9]\\d*$");
- for (const auto& path : cuttlefish::DirectoryContents(runtime_dir_parent)) {
- std::string absl_path = runtime_dir_parent + "/" + path;
- if((path.rfind(runtime_dirs_basename, 0) == 0) && std::regex_match(path, instance_dir_regex) &&
- cuttlefish::DirectoryExists(absl_path)) {
- paths.push_back(absl_path);
- }
- }
- paths.push_back(FLAGS_instance_dir);
- return CleanPriorFiles(paths, preserving);
-}
-
void ValidateAdbModeFlag(const cuttlefish::CuttlefishConfig& config) {
auto adb_modes = config.adb_mode();
adb_modes.erase(cuttlefish::AdbMode::Unknown);
@@ -976,64 +933,31 @@
ss.str("");
}
}
- if (!CleanPriorFiles(preserving)) {
+ if (!CleanPriorFiles(preserving, FLAGS_assembly_dir, FLAGS_instance_dir)) {
LOG(ERROR) << "Failed to clean prior files";
exit(AssemblerExitCodes::kPrioFilesCleanupError);
}
// Create assembly directory if it doesn't exist.
- if (!cuttlefish::DirectoryExists(FLAGS_assembly_dir.c_str())) {
- LOG(DEBUG) << "Setting up " << FLAGS_assembly_dir;
- if (mkdir(FLAGS_assembly_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
- && errno != EEXIST) {
- LOG(ERROR) << "Failed to create assembly directory: "
- << FLAGS_assembly_dir << ". Error: " << errno;
- exit(AssemblerExitCodes::kAssemblyDirCreationError);
- }
- }
+ EnsureDirectoryExistsOrExit(
+ FLAGS_assembly_dir, AssemblerExitCodes::kAssemblyDirCreationError);
if (log->LinkAtCwd(config.AssemblyPath("assemble_cvd.log"))) {
LOG(ERROR) << "Unable to persist assemble_cvd log at "
<< config.AssemblyPath("assemble_cvd.log")
<< ": " << log->StrError();
}
std::string disk_hole_dir = FLAGS_assembly_dir + "/disk_hole";
- if (!cuttlefish::DirectoryExists(disk_hole_dir.c_str())) {
- LOG(DEBUG) << "Setting up " << disk_hole_dir << "/disk_hole";
- if (mkdir(disk_hole_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
- && errno != EEXIST) {
- LOG(ERROR) << "Failed to create assembly directory: "
- << disk_hole_dir << ". Error: " << errno;
- exit(AssemblerExitCodes::kAssemblyDirCreationError);
- }
- }
+ EnsureDirectoryExistsOrExit(
+ disk_hole_dir, cuttlefish::kAssemblyDirCreationError);
for (const auto& instance : config.Instances()) {
// Create instance directory if it doesn't exist.
- if (!cuttlefish::DirectoryExists(instance.instance_dir().c_str())) {
- LOG(DEBUG) << "Setting up " << FLAGS_instance_dir << ".N";
- if (mkdir(instance.instance_dir().c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
- && errno != EEXIST) {
- LOG(ERROR) << "Failed to create instance directory: "
- << FLAGS_instance_dir << ". Error: " << errno;
- exit(AssemblerExitCodes::kInstanceDirCreationError);
- }
- }
+ EnsureDirectoryExistsOrExit(
+ instance.instance_dir(), cuttlefish::kInstanceDirCreationError);
auto internal_dir = instance.instance_dir() + "/" + cuttlefish::kInternalDirName;
- if (!cuttlefish::DirectoryExists(internal_dir)) {
- if (mkdir(internal_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
- && errno != EEXIST) {
- LOG(ERROR) << "Failed to create internal instance directory: "
- << internal_dir << ". Error: " << errno;
- exit(AssemblerExitCodes::kInstanceDirCreationError);
- }
- }
+ EnsureDirectoryExistsOrExit(
+ internal_dir, cuttlefish::kInstanceDirCreationError);
auto shared_dir = instance.instance_dir() + "/" + cuttlefish::kSharedDirName;
- if (!cuttlefish::DirectoryExists(shared_dir)) {
- if (mkdir(shared_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
- && errno != EEXIST) {
- LOG(ERROR) << "Failed to create shared instance directory: "
- << shared_dir << ". Error: " << errno;
- exit(AssemblerExitCodes::kInstanceDirCreationError);
- }
- }
+ EnsureDirectoryExistsOrExit(
+ shared_dir, cuttlefish::kInstanceDirCreationError);
}
if (!SaveConfig(config)) {
LOG(ERROR) << "Failed to initialize configuration";
@@ -1065,116 +989,6 @@
return config.AssemblyPath("cuttlefish_config.json");
}
-std::optional<IfaceConfig> AcquireIfaces(int num) {
- IfaceConfig config{};
- if (!FLAGS_use_allocd) {
- config.mobile_tap.name = StrForInstance("cvd-mtap-", num);
- config.mobile_tap.resource_id = 0;
- config.mobile_tap.session_id = 0;
-
- config.wireless_tap.name = StrForInstance("cvd-wtap-", num);
- config.wireless_tap.resource_id = 0;
- config.wireless_tap.session_id = 0;
- return config;
- }
- return RequestIfaces();
-}
-
-std::optional<IfaceConfig> RequestIfaces() {
- IfaceConfig config{};
-
- cuttlefish::SharedFD allocd_sock = cuttlefish::SharedFD::SocketLocalClient(
- cuttlefish::kDefaultLocation, false, SOCK_STREAM);
- if (!allocd_sock->IsOpen()) {
- LOG(FATAL) << "Unable to connect to allocd on "
- << cuttlefish::kDefaultLocation << ": "
- << allocd_sock->StrError();
- exit(cuttlefish::kAllocdConnectionError);
- }
-
- Json::Value resource_config;
- Json::Value request_list;
- Json::Value req;
- req["request_type"] = "create_interface";
- req["uid"] = geteuid();
- req["iface_type"] = "mtap";
- request_list.append(req);
- req["iface_type"] = "wtap";
- request_list.append(req);
-
- resource_config["config_request"]["request_list"] = request_list;
-
- if (!cuttlefish::SendJsonMsg(allocd_sock, resource_config)) {
- LOG(FATAL) << "Failed to send JSON to allocd\n";
- return std::nullopt;
- }
-
- auto resp_opt = cuttlefish::RecvJsonMsg(allocd_sock);
- if (!resp_opt.has_value()) {
- LOG(FATAL) << "Bad Response from allocd\n";
- exit(cuttlefish::kAllocdConnectionError);
- }
- auto resp = resp_opt.value();
-
- if (!resp.isMember("config_status") || !resp["config_status"].isString()) {
- LOG(FATAL) << "Bad response from allocd: " << resp;
- exit(cuttlefish::kAllocdConnectionError);
- }
-
- if (resp["config_status"].asString() !=
- cuttlefish::StatusToStr(cuttlefish::RequestStatus::Success)) {
- LOG(FATAL) << "Failed to allocate interfaces " << resp;
- exit(cuttlefish::kAllocdConnectionError);
- }
-
- if (!resp.isMember("session_id") || !resp["session_id"].isUInt()) {
- LOG(FATAL) << "Bad response from allocd: " << resp;
- exit(cuttlefish::kAllocdConnectionError);
- }
- auto session_id = resp["session_id"].asUInt();
-
- if (!resp.isMember("response_list") || !resp["response_list"].isArray()) {
- LOG(FATAL) << "Bad response from allocd: " << resp;
- exit(cuttlefish::kAllocdConnectionError);
- }
-
- Json::Value resp_list = resp["response_list"];
- Json::Value mtap_resp;
- Json::Value wifi_resp;
- for (Json::Value::ArrayIndex i = 0; i != resp_list.size(); ++i) {
- auto ty = cuttlefish::StrToIfaceTy(resp_list[i]["iface_type"].asString());
-
- switch (ty) {
- case cuttlefish::IfaceType::mtap: {
- mtap_resp = resp_list[i];
- break;
- }
- case cuttlefish::IfaceType::wtap: {
- wifi_resp = resp_list[i];
- break;
- }
- default: {
- break;
- }
- }
- }
-
- if (!mtap_resp.isMember("iface_type")) {
- LOG(ERROR) << "Missing mtap response from allocd";
- return std::nullopt;
- }
- if (!wifi_resp.isMember("iface_type")) {
- LOG(ERROR) << "Missing wtap response from allocd";
- return std::nullopt;
- }
-
- config.mobile_tap.name = mtap_resp["iface_name"].asString();
- config.mobile_tap.resource_id = mtap_resp["resource_id"].asUInt();
- config.mobile_tap.session_id = session_id;
-
- config.wireless_tap.name = wifi_resp["iface_name"].asString();
- config.wireless_tap.resource_id = wifi_resp["resource_id"].asUInt();
- config.wireless_tap.session_id = session_id;
-
- return config;
+std::string GetCuttlefishEnvPath() {
+ return cuttlefish::StringFromEnv("HOME", ".") + "/.cuttlefish.sh";
}
diff --git a/host/commands/assemble_cvd/flags.h b/host/commands/assemble_cvd/flags.h
index 417ee49..7322854 100644
--- a/host/commands/assemble_cvd/flags.h
+++ b/host/commands/assemble_cvd/flags.h
@@ -9,19 +9,4 @@
const cuttlefish::CuttlefishConfig* InitFilesystemAndCreateConfig(
int* argc, char*** argv, cuttlefish::FetcherConfig config);
std::string GetConfigFilePath(const cuttlefish::CuttlefishConfig& config);
-
-struct IfaceData {
- std::string name;
- uint32_t session_id;
- uint32_t resource_id;
-};
-
-struct IfaceConfig {
- IfaceData mobile_tap;
- IfaceData wireless_tap;
-};
-
-// Acquires interfaces from the resource allocator daemon if it is enabled,
-// or fallse back to using the static resources created by the debian package
-std::optional<IfaceConfig> AcquireIfaces(int num);
-std::optional<IfaceConfig> RequestIfaces();
+std::string GetCuttlefishEnvPath();
diff --git a/host/commands/kernel_log_monitor/kernel_log_server.cc b/host/commands/kernel_log_monitor/kernel_log_server.cc
index 089706f..66fafab 100644
--- a/host/commands/kernel_log_monitor/kernel_log_server.cc
+++ b/host/commands/kernel_log_monitor/kernel_log_server.cc
@@ -41,6 +41,7 @@
{cuttlefish::kMobileNetworkConnectedMessage,
monitor::Event::MobileNetworkConnected},
{cuttlefish::kWifiConnectedMessage, monitor::Event::WifiNetworkConnected},
+ {cuttlefish::kEthernetConnectedMessage, monitor::Event::EthernetNetworkConnected},
// TODO(b/131864854): Replace this with a string less likely to change
{"init: starting service 'adbd'...", monitor::Event::AdbdStarted},
{cuttlefish::kScreenChangedMessage, monitor::Event::ScreenChanged},
diff --git a/host/commands/kernel_log_monitor/kernel_log_server.h b/host/commands/kernel_log_monitor/kernel_log_server.h
index 13e6a8a..659a372 100644
--- a/host/commands/kernel_log_monitor/kernel_log_server.h
+++ b/host/commands/kernel_log_monitor/kernel_log_server.h
@@ -36,6 +36,7 @@
MobileNetworkConnected = 4,
AdbdStarted = 5,
ScreenChanged = 6,
+ EthernetNetworkConnected = 7,
};
enum class SubscriptionAction {
diff --git a/host/commands/run_cvd/launch.cc b/host/commands/run_cvd/launch.cc
index 26c01e6..b45fe58 100644
--- a/host/commands/run_cvd/launch.cc
+++ b/host/commands/run_cvd/launch.cc
@@ -4,6 +4,7 @@
#include <sys/types.h>
#include <android-base/logging.h>
+#include <android-base/strings.h>
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/files.h"
@@ -311,6 +312,8 @@
webrtc.AddParameter("-kernel_log_events_fd=", kernel_log_events_pipe);
+ LaunchCustomActionServers(webrtc, process_monitor, config);
+
// TODO get from launcher params
process_monitor->StartSubprocess(std::move(webrtc),
GetOnSubprocessExitCallback(config));
@@ -530,6 +533,40 @@
GetOnSubprocessExitCallback(config));
}
+void LaunchCustomActionServers(cuttlefish::Command& webrtc_cmd,
+ cuttlefish::ProcessMonitor* process_monitor,
+ const cuttlefish::CuttlefishConfig& config) {
+ bool first = true;
+ for (const auto& custom_action : config.custom_actions()) {
+ if (custom_action.server) {
+ // Create a socket pair that will be used for communication between
+ // WebRTC and the action server.
+ cuttlefish::SharedFD webrtc_socket, action_server_socket;
+ if (!cuttlefish::SharedFD::SocketPair(AF_LOCAL, SOCK_STREAM, 0,
+ &webrtc_socket, &action_server_socket)) {
+ LOG(ERROR) << "Unable to create custom action server socket pair: "
+ << strerror(errno);
+ continue;
+ }
+
+ // Launch the action server, providing its socket pair fd as the only argument.
+ std::string binary = "bin/" + *(custom_action.server);
+ cuttlefish::Command command(cuttlefish::DefaultHostArtifactsPath(binary));
+ command.AddParameter(action_server_socket);
+ process_monitor->StartSubprocess(std::move(command),
+ GetOnSubprocessExitCallback(config));
+
+ // Pass the WebRTC socket pair fd to WebRTC.
+ if (first) {
+ first = false;
+ webrtc_cmd.AddParameter("-action_servers=", *custom_action.server, ":", webrtc_socket);
+ } else {
+ webrtc_cmd.AppendToLastParameter(",", *custom_action.server, ":", webrtc_socket);
+ }
+ }
+ }
+}
+
void LaunchVerhicleHalServerIfEnabled(const cuttlefish::CuttlefishConfig& config,
cuttlefish::ProcessMonitor* process_monitor) {
if (!config.enable_vehicle_hal_grpc_server() ||
diff --git a/host/commands/run_cvd/launch.h b/host/commands/run_cvd/launch.h
index a727c11..d432fa8 100644
--- a/host/commands/run_cvd/launch.h
+++ b/host/commands/run_cvd/launch.h
@@ -46,6 +46,10 @@
void LaunchSecureEnvironment(cuttlefish::ProcessMonitor* process_monitor,
const cuttlefish::CuttlefishConfig& config);
+void LaunchCustomActionServers(cuttlefish::Command& webrtc_cmd,
+ cuttlefish::ProcessMonitor* process_monitor,
+ const cuttlefish::CuttlefishConfig& config);
+
void LaunchVerhicleHalServerIfEnabled(const cuttlefish::CuttlefishConfig& config,
cuttlefish::ProcessMonitor* process_monitor);
diff --git a/host/commands/run_cvd/main.cc b/host/commands/run_cvd/main.cc
index b0b73f4..9b464c3 100644
--- a/host/commands/run_cvd/main.cc
+++ b/host/commands/run_cvd/main.cc
@@ -500,6 +500,9 @@
} else if (used_tap_devices.count(instance.mobile_tap_name())) {
LOG(ERROR) << "Mobile TAP device already in use";
return RunnerExitCodes::kTapDeviceInUse;
+ } else if (config->ethernet() &&
+ used_tap_devices.count(instance.ethernet_tap_name())) {
+ LOG(ERROR) << "Ethernet TAP device already in use";
}
auto vm_manager = GetVmManager(config->vm_manager());
diff --git a/host/example_custom_actions/Android.bp b/host/example_custom_actions/Android.bp
new file mode 100644
index 0000000..65b3ca4
--- /dev/null
+++ b/host/example_custom_actions/Android.bp
@@ -0,0 +1,24 @@
+cc_binary_host {
+ name: "cuttlefish_example_action_server",
+ srcs: ["main.cpp"],
+ defaults: [
+ "cuttlefish_host_only",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libutils",
+ "libjsoncpp",
+ "libcuttlefish_fs",
+ "libcuttlefish_utils",
+ ],
+ static_libs: [
+ "libcuttlefish_host_config",
+ ],
+}
+
+prebuilt_etc_host {
+ name: "cuttlefish_example_action_config.json",
+ src: "custom_action_config.json",
+ sub_dir: "cvd_custom_action_config",
+}
diff --git a/host/example_custom_actions/README.md b/host/example_custom_actions/README.md
new file mode 100644
index 0000000..de3764d
--- /dev/null
+++ b/host/example_custom_actions/README.md
@@ -0,0 +1,12 @@
+To try out the custom action config and action server in this path, set the
+following build vars:
+
+```
+SOONG_CONFIG_NAMESPACES += cvd
+SOONG_CONFIG_cvd += custom_action_config custom_action_servers
+
+SOONG_CONFIG_cvd_custom_action_config := cuttlefish_example_action_config.json
+SOONG_CONFIG_cvd_custom_action_servers += cuttlefish_example_action_server
+```
+
+See `device/google/cuttlefish/build/README.md` for more information.
diff --git a/host/example_custom_actions/custom_action_config.json b/host/example_custom_actions/custom_action_config.json
new file mode 100644
index 0000000..56dae0d
--- /dev/null
+++ b/host/example_custom_actions/custom_action_config.json
@@ -0,0 +1,25 @@
+[
+ {
+ "shell_command":"am start -a android.intent.action.VIEW -d https://www.android.com/",
+ "button":{
+ "command":"web",
+ "title":"Web Page",
+ "icon_name":"language"
+ }
+ },
+ {
+ "server":"cuttlefish_example_action_server",
+ "buttons":[
+ {
+ "command":"settings",
+ "title":"Quick Settings",
+ "icon_name":"settings"
+ },
+ {
+ "command":"alert",
+ "title":"Do Not Disturb",
+ "icon_name":"notifications_paused"
+ }
+ ]
+ }
+]
diff --git a/host/example_custom_actions/main.cpp b/host/example_custom_actions/main.cpp
new file mode 100644
index 0000000..3f2c8d9
--- /dev/null
+++ b/host/example_custom_actions/main.cpp
@@ -0,0 +1,78 @@
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <sys/socket.h>
+
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "host/libs/config/cuttlefish_config.h"
+
+// Messages are always 128 bytes.
+#define MESSAGE_SIZE 128
+
+using cuttlefish::SharedFD;
+
+int main(int argc, char** argv) {
+ if (argc <= 1) {
+ return 1;
+ }
+
+ // Connect to WebRTC
+ int fd = std::atoi(argv[1]);
+ LOG(INFO) << "Connecting to WebRTC server...";
+ SharedFD webrtc_socket = SharedFD::Dup(fd);
+ close(fd);
+ if (webrtc_socket->IsOpen()) {
+ LOG(INFO) << "Connected";
+ } else {
+ LOG(ERROR) << "Could not connect, exiting...";
+ return 1;
+ }
+
+ // Track state for our two commands.
+ bool statusbar_expanded = false;
+ bool dnd_on = false;
+
+ char buf[MESSAGE_SIZE];
+ while (1) {
+ // Read the command message from the socket.
+ if (!webrtc_socket->IsOpen()) {
+ LOG(WARNING) << "WebRTC was closed.";
+ break;
+ }
+ if (cuttlefish::ReadExact(webrtc_socket, buf, MESSAGE_SIZE) !=
+ MESSAGE_SIZE) {
+ LOG(WARNING) << "Failed to read the correct number of bytes.";
+ break;
+ }
+ auto split = android::base::Split(buf, ":");
+ std::string command = split[0];
+ std::string state = split[1];
+
+ // Ignore button-release events, when state != down.
+ if (state != "down") {
+ continue;
+ }
+
+ // Demonstrate two commands. For demonstration purposes these two
+ // commands use adb shell, but commands can execute any action you choose.
+ std::string adb_shell_command =
+ cuttlefish::DefaultHostArtifactsPath("bin/adb");
+ if (command == "settings") {
+ adb_shell_command += " shell cmd statusbar ";
+ adb_shell_command += statusbar_expanded ? "collapse" : "expand-settings";
+ statusbar_expanded = !statusbar_expanded;
+ } else if (command == "alert") {
+ adb_shell_command += " shell cmd notification set_dnd ";
+ adb_shell_command += dnd_on ? "off" : "on";
+ dnd_on = !dnd_on;
+ } else {
+ LOG(WARNING) << "Unexpected command: " << buf;
+ }
+
+ if (!adb_shell_command.empty()) {
+ if (system(adb_shell_command.c_str()) != 0) {
+ LOG(ERROR) << "Failed to run command: " << adb_shell_command;
+ }
+ }
+ }
+}
diff --git a/host/frontend/vnc_server/frame_buffer_watcher.cpp b/host/frontend/vnc_server/frame_buffer_watcher.cpp
index f195821..a8fd72f 100644
--- a/host/frontend/vnc_server/frame_buffer_watcher.cpp
+++ b/host/frontend/vnc_server/frame_buffer_watcher.cpp
@@ -190,3 +190,11 @@
int FrameBufferWatcher::StripesPerFrame() {
return SimulatedHWComposer::NumberOfStripes();
}
+
+void FrameBufferWatcher::IncClientCount() {
+ hwcomposer.ReportClientsConnected();
+}
+
+void FrameBufferWatcher::DecClientCount() {
+ // Do nothing
+}
diff --git a/host/frontend/vnc_server/frame_buffer_watcher.h b/host/frontend/vnc_server/frame_buffer_watcher.h
index 909f092..d147642 100644
--- a/host/frontend/vnc_server/frame_buffer_watcher.h
+++ b/host/frontend/vnc_server/frame_buffer_watcher.h
@@ -38,6 +38,9 @@
StripePtrVec StripesNewerThan(ScreenOrientation orientation,
const SeqNumberVec& seq_num) const;
+ void IncClientCount();
+ void DecClientCount();
+
static int StripesPerFrame();
private:
diff --git a/host/frontend/vnc_server/simulated_hw_composer.cpp b/host/frontend/vnc_server/simulated_hw_composer.cpp
index 811151e..32aa387 100644
--- a/host/frontend/vnc_server/simulated_hw_composer.cpp
+++ b/host/frontend/vnc_server/simulated_hw_composer.cpp
@@ -126,3 +126,7 @@
}
int SimulatedHWComposer::NumberOfStripes() { return kNumStripes; }
+
+void SimulatedHWComposer::ReportClientsConnected() {
+ screen_connector_->ReportClientsConnected(true);
+}
diff --git a/host/frontend/vnc_server/simulated_hw_composer.h b/host/frontend/vnc_server/simulated_hw_composer.h
index 1b6cf87..e0da859 100644
--- a/host/frontend/vnc_server/simulated_hw_composer.h
+++ b/host/frontend/vnc_server/simulated_hw_composer.h
@@ -39,6 +39,8 @@
Stripe GetNewStripe();
+ void ReportClientsConnected();
+
// NOTE not constexpr on purpose
static int NumberOfStripes();
diff --git a/host/frontend/vnc_server/vnc_server.cpp b/host/frontend/vnc_server/vnc_server.cpp
index beb227f..dd489b8 100644
--- a/host/frontend/vnc_server/vnc_server.cpp
+++ b/host/frontend/vnc_server/vnc_server.cpp
@@ -53,7 +53,9 @@
// data members. In the current setup, if the VncServer is destroyed with
// clients still running, the clients will all be left with dangling
// pointers.
+ frame_buffer_watcher_.IncClientCount();
VncClientConnection client(std::move(sock), virtual_inputs_, &bb_,
aggressive_);
client.StartSession();
+ frame_buffer_watcher_.DecClientCount();
}
diff --git a/host/frontend/webrtc/connection_observer.cpp b/host/frontend/webrtc/connection_observer.cpp
index 3ab1cf8..2a22ec0 100644
--- a/host/frontend/webrtc/connection_observer.cpp
+++ b/host/frontend/webrtc/connection_observer.cpp
@@ -18,6 +18,7 @@
#include <linux/input.h>
+#include <map>
#include <thread>
#include <vector>
@@ -81,16 +82,25 @@
public:
ConnectionObserverImpl(cuttlefish::InputSockets& input_sockets,
cuttlefish::SharedFD kernel_log_events_fd,
+ std::map<std::string, cuttlefish::SharedFD>
+ commands_to_custom_action_servers,
std::weak_ptr<DisplayHandler> display_handler)
: input_sockets_(input_sockets),
kernel_log_events_client_(kernel_log_events_fd),
+ commands_to_custom_action_servers_(commands_to_custom_action_servers),
weak_display_handler_(display_handler) {}
- virtual ~ConnectionObserverImpl() = default;
+ virtual ~ConnectionObserverImpl() {
+ auto display_handler = weak_display_handler_.lock();
+ if (display_handler) {
+ display_handler->DecClientCount();
+ }
+ }
void OnConnected(std::function<void(const uint8_t *, size_t, bool)>
/*ctrl_msg_sender*/) override {
auto display_handler = weak_display_handler_.lock();
if (display_handler) {
+ display_handler->IncClientCount();
// A long time may pass before the next frame comes up from the guest.
// Send the last one to avoid showing a black screen to the user during
// that time.
@@ -181,6 +191,15 @@
OnKeyboardEvent(KEY_VOLUMEDOWN, state == "down");
} else if (command == "volumeup") {
OnKeyboardEvent(KEY_VOLUMEUP, 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
+ // - Example: my_button:down
+ std::string action_server_message = command + ":" + 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.
@@ -192,6 +211,7 @@
cuttlefish::SharedFD kernel_log_events_client_;
std::shared_ptr<cuttlefish::webrtc_streaming::AdbHandler> adb_handler_;
std::shared_ptr<cuttlefish::webrtc_streaming::KernelLogEventsHandler> kernel_log_events_handler_;
+ std::map<std::string, cuttlefish::SharedFD> commands_to_custom_action_servers_;
std::weak_ptr<DisplayHandler> weak_display_handler_;
};
@@ -206,9 +226,19 @@
return std::shared_ptr<cuttlefish::webrtc_streaming::ConnectionObserver>(
new ConnectionObserverImpl(input_sockets_,
kernel_log_events_fd_,
+ commands_to_custom_action_servers_,
weak_display_handler_));
}
+void CfConnectionObserverFactory::AddCustomActionServer(
+ cuttlefish::SharedFD custom_action_server_fd,
+ const std::vector<std::string>& commands) {
+ for (const std::string& command : commands) {
+ LOG(DEBUG) << "Action server is listening to command: " << command;
+ commands_to_custom_action_servers_[command] = custom_action_server_fd;
+ }
+}
+
void CfConnectionObserverFactory::SetDisplayHandler(
std::weak_ptr<DisplayHandler> display_handler) {
weak_display_handler_ = display_handler;
diff --git a/host/frontend/webrtc/connection_observer.h b/host/frontend/webrtc/connection_observer.h
index e2c185a..3d12807 100644
--- a/host/frontend/webrtc/connection_observer.h
+++ b/host/frontend/webrtc/connection_observer.h
@@ -16,6 +16,7 @@
#pragma once
+#include <map>
#include <memory>
#include "common/libs/fs/shared_fd.h"
@@ -41,11 +42,16 @@
std::shared_ptr<cuttlefish::webrtc_streaming::ConnectionObserver> CreateObserver()
override;
+ void AddCustomActionServer(cuttlefish::SharedFD custom_action_server_fd,
+ const std::vector<std::string>& commands);
+
void SetDisplayHandler(std::weak_ptr<DisplayHandler> display_handler);
private:
cuttlefish::InputSockets& input_sockets_;
cuttlefish::SharedFD kernel_log_events_fd_;
+ std::map<std::string, cuttlefish::SharedFD>
+ commands_to_custom_action_servers_;
std::weak_ptr<DisplayHandler> weak_display_handler_;
};
diff --git a/host/frontend/webrtc/display_handler.cpp b/host/frontend/webrtc/display_handler.cpp
index a185613..256b96b 100644
--- a/host/frontend/webrtc/display_handler.cpp
+++ b/host/frontend/webrtc/display_handler.cpp
@@ -76,4 +76,18 @@
}
}
+void DisplayHandler::IncClientCount() {
+ client_count_++;
+ if (client_count_ == 1) {
+ screen_connector_->ReportClientsConnected(true);
+ }
+}
+
+void DisplayHandler::DecClientCount() {
+ client_count_--;
+ if (client_count_ == 0) {
+ screen_connector_->ReportClientsConnected(false);
+ }
+}
+
} // namespace cuttlefish
diff --git a/host/frontend/webrtc/display_handler.h b/host/frontend/webrtc/display_handler.h
index 236c26a..b87cb57 100644
--- a/host/frontend/webrtc/display_handler.h
+++ b/host/frontend/webrtc/display_handler.h
@@ -33,11 +33,15 @@
[[noreturn]] void Loop();
void SendLastFrame();
+ void IncClientCount();
+ void DecClientCount();
+
private:
std::shared_ptr<webrtc_streaming::VideoSink> display_sink_;
ScreenConnector* screen_connector_;
std::shared_ptr<webrtc_streaming::VideoFrameBuffer> last_buffer_;
std::mutex last_buffer_mutex_;
std::mutex next_frame_mutex_;
+ int client_count_ = 0;
};
} // namespace cuttlefish
diff --git a/host/frontend/webrtc/lib/streamer.cpp b/host/frontend/webrtc/lib/streamer.cpp
index afa3a97..c8c3585 100644
--- a/host/frontend/webrtc/lib/streamer.cpp
+++ b/host/frontend/webrtc/lib/streamer.cpp
@@ -51,6 +51,11 @@
constexpr auto kCpusField = "cpus";
constexpr auto kMemoryMbField = "memory_mb";
constexpr auto kHardwareField = "hardware";
+constexpr auto kControlPanelButtonCommand = "command";
+constexpr auto kControlPanelButtonTitle = "title";
+constexpr auto kControlPanelButtonIconName = "icon_name";
+constexpr auto kControlPanelButtonShellCommand = "shell_command";
+constexpr auto kCustomControlPanelButtonsField = "custom_control_panel_buttons";
void SendJson(WsConnection* ws_conn, const Json::Value& data) {
Json::FastWriter json_writer;
@@ -92,6 +97,13 @@
int memory_mb;
};
+struct ControlPanelButtonDescriptor {
+ std::string command;
+ std::string title;
+ std::string icon_name;
+ std::optional<std::string> shell_command;
+};
+
// TODO (jemoreira): move to a place in common with the signaling server
struct OperatorServerConfig {
std::vector<webrtc::PeerConnectionInterface::IceServer> servers;
@@ -130,6 +142,7 @@
std::map<int, std::shared_ptr<ClientHandler>> clients_;
std::weak_ptr<OperatorObserver> operator_observer_;
HardwareDescriptor hardware_;
+ std::vector<ControlPanelButtonDescriptor> custom_control_panel_buttons_;
};
Streamer::Streamer(std::unique_ptr<Streamer::Impl> impl)
@@ -203,6 +216,15 @@
impl_->hardware_.memory_mb = memory_mb;
}
+void Streamer::AddCustomControlPanelButton(
+ 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};
+ impl_->custom_control_panel_buttons_.push_back(button);
+}
+
void Streamer::AddAudio(const std::string& label) {
// Usually called from an application thread
// TODO (b/128328845): audio support. Use signal_thread_->Invoke<>();
@@ -263,6 +285,18 @@
hardware[kCpusField] = hardware_.cpus;
hardware[kMemoryMbField] = hardware_.memory_mb;
device_info[kHardwareField] = hardware;
+ Json::Value custom_control_panel_buttons(Json::arrayValue);
+ for (const auto& button : custom_control_panel_buttons_) {
+ Json::Value button_entry;
+ button_entry[kControlPanelButtonCommand] = button.command;
+ button_entry[kControlPanelButtonTitle] = button.title;
+ button_entry[kControlPanelButtonIconName] = button.icon_name;
+ if (button.shell_command) {
+ button_entry[kControlPanelButtonShellCommand] = *(button.shell_command);
+ }
+ custom_control_panel_buttons.append(button_entry);
+ }
+ device_info[kCustomControlPanelButtonsField] = custom_control_panel_buttons;
register_obj[cuttlefish::webrtc_signaling::kDeviceInfoField] = device_info;
SendJson(server_connection_.get(), register_obj);
// Do this last as OnRegistered() is user code and may take some time to
diff --git a/host/frontend/webrtc/lib/streamer.h b/host/frontend/webrtc/lib/streamer.h
index 7788dc7..f39d804 100644
--- a/host/frontend/webrtc/lib/streamer.h
+++ b/host/frontend/webrtc/lib/streamer.h
@@ -19,6 +19,7 @@
#include <functional>
#include <memory>
#include <mutex>
+#include <optional>
#include <string>
#include <utility>
#include <vector>
@@ -80,6 +81,14 @@
void SetHardwareSpecs(int cpus, int memory_mb);
+ // 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(
+ const std::string& command, const std::string& title,
+ const std::string& icon_name,
+ const std::optional<std::string>& shell_command = std::nullopt);
+
// TODO (b/128328845): Implement audio, return a shared_ptr to a class
// equivalent to webrtc::AudioSinkInterface.
void AddAudio(const std::string& label);
diff --git a/host/frontend/webrtc/main.cpp b/host/frontend/webrtc/main.cpp
index d772a84..85c6660 100644
--- a/host/frontend/webrtc/main.cpp
+++ b/host/frontend/webrtc/main.cpp
@@ -22,6 +22,7 @@
#include <vector>
#include <android-base/logging.h>
+#include <android-base/strings.h>
#include <gflags/gflags.h>
#include <libyuv.h>
@@ -38,6 +39,9 @@
DEFINE_int32(keyboard_fd, -1, "An fd to listen on for keyboard connections.");
DEFINE_int32(frame_server_fd, -1, "An fd to listen on for frame updates");
DEFINE_int32(kernel_log_events_fd, -1, "An fd to listen on for kernel log events.");
+DEFINE_string(action_servers, "",
+ "A comma-separated list of server_name:fd pairs, "
+ "where each entry corresponds to one custom action server.");
DEFINE_bool(write_virtio_input, false,
"Whether to send input events in virtio format.");
@@ -174,6 +178,60 @@
streamer->SetHardwareSpecs(cvd_config->cpus(), cvd_config->memory_mb());
+ // Parse the -action_servers flag, storing a map of action server name -> fd
+ std::map<std::string, int> action_server_fds;
+ for (const std::string& action_server : android::base::Split(FLAGS_action_servers, ",")) {
+ if (action_server.empty()) {
+ continue;
+ }
+ const std::vector<std::string> server_and_fd = android::base::Split(action_server, ":");
+ CHECK(server_and_fd.size() == 2) << "Wrong format for action server flag: " << action_server;
+ std::string server = server_and_fd[0];
+ int fd = std::stoi(server_and_fd[1]);
+ action_server_fds[server] = fd;
+ }
+
+ for (const auto& custom_action : cvd_config->custom_actions()) {
+ if (custom_action.shell_command) {
+ if (custom_action.buttons.size() != 1) {
+ LOG(FATAL) << "Expected exactly one button for custom action command: "
+ << *(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) {
+ if (action_server_fds.find(*(custom_action.server)) !=
+ action_server_fds.end()) {
+ LOG(INFO) << "Connecting to custom action server "
+ << *(custom_action.server);
+
+ int fd = action_server_fds[*(custom_action.server)];
+ cuttlefish::SharedFD custom_action_server = cuttlefish::SharedFD::Dup(fd);
+ close(fd);
+
+ if (custom_action_server->IsOpen()) {
+ std::vector<std::string> commands_for_this_server;
+ for (const auto& button : custom_action.buttons) {
+ streamer->AddCustomControlPanelButton(button.command, button.title,
+ button.icon_name);
+ commands_for_this_server.push_back(button.command);
+ }
+ observer_factory->AddCustomActionServer(custom_action_server,
+ commands_for_this_server);
+ } else {
+ LOG(ERROR) << "Error connecting to custom action server: "
+ << *(custom_action.server);
+ }
+ } else {
+ LOG(ERROR) << "Custom action server not provided as command line flag: "
+ << *(custom_action.server);
+ }
+ }
+ }
+
std::shared_ptr<cuttlefish::webrtc_streaming::OperatorObserver> operator_observer(
new CfOperatorObserver());
streamer->Register(operator_observer);
diff --git a/host/frontend/webrtc_operator/assets/index.html b/host/frontend/webrtc_operator/assets/index.html
index 972bba6..2b15302 100644
--- a/host/frontend/webrtc_operator/assets/index.html
+++ b/host/frontend/webrtc_operator/assets/index.html
@@ -36,6 +36,9 @@
<hr>
<div id='control_panel'>
<h2>Device Controls</h2>
+ <div id='control_panel_default_buttons'></div>
+ <h2 id='custom_controls_title'>Custom Controls</h2>
+ <div id='control_panel_custom_buttons'></div>
</div>
<div id='device_details'>
<h2>Device Details</h2>
diff --git a/host/frontend/webrtc_operator/assets/js/app.js b/host/frontend/webrtc_operator/assets/js/app.js
index b2e6293..b1f97c9 100644
--- a/host/frontend/webrtc_operator/assets/js/app.js
+++ b/host/frontend/webrtc_operator/assets/js/app.js
@@ -112,9 +112,10 @@
window.onresize = resizeDeviceView;
function createControlPanelButton(command, title, icon_name,
- listener=onControlPanelButton) {
+ listener=onControlPanelButton,
+ parent_id='control_panel_default_buttons') {
let button = document.createElement('button');
- document.getElementById('control_panel').appendChild(button);
+ document.getElementById(parent_id).appendChild(button);
button.title = title;
button.dataset.command = command;
// Capture mousedown/up/out commands instead of click to enable
@@ -158,6 +159,23 @@
startMouseTracking(); // TODO stopMouseTracking() when disconnected
updateDeviceHardwareDetails(deviceConnection.description.hardware);
updateDeviceDisplayDetails(deviceConnection.description.displays[0]);
+ if (deviceConnection.description.custom_control_panel_buttons.length == 0) {
+ document.getElementById('custom_controls_title').style.visibility = 'hidden';
+ } else {
+ for (const button of deviceConnection.description.custom_control_panel_buttons) {
+ if (button.shell_command) {
+ // This button's command is handled by sending an ADB shell command.
+ createControlPanelButton(button.command, button.title, button.icon_name,
+ e => onCustomShellButton(button.shell_command, e),
+ 'control_panel_custom_buttons');
+ } else {
+ // This button's command is handled by custom action server.
+ createControlPanelButton(button.command, button.title, button.icon_name,
+ onControlPanelButton,
+ 'control_panel_custom_buttons');
+ }
+ }
+ }
deviceConnection.onControlMessage(msg => onControlMessage(msg));
// Start the screen as hidden. Only show when data is ready.
deviceScreen.style.visibility = 'hidden';
@@ -219,6 +237,14 @@
adbShell('/vendor/bin/cuttlefish_rotate ' + (rotation == 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.
+ init_adb(deviceConnection);
+ if (e.type == 'mousedown') {
+ adbShell(shell_command);
+ }
+ }
function startMouseTracking() {
if (window.PointerEvent) {
diff --git a/host/libs/allocd/alloc_utils.cpp b/host/libs/allocd/alloc_utils.cpp
index 883b42c..8c91686 100644
--- a/host/libs/allocd/alloc_utils.cpp
+++ b/host/libs/allocd/alloc_utils.cpp
@@ -63,17 +63,13 @@
return status == 0;
}
-bool CreateWirelessIface(const std::string& name, bool has_ipv4_bridge,
- bool has_ipv6_bridge, bool use_ebtables_legacy) {
+bool CreateEthernetIface(const std::string& name, const std::string& bridge_name,
+ bool has_ipv4_bridge, bool has_ipv6_bridge,
+ bool use_ebtables_legacy) {
// assume bridge exists
- WirelessNetworkConfig config{false, false, false};
+ EthernetNetworkConfig config{false, false, false};
- // TODO (paulkirth): change this to cvd-wbr, to test w/ today's debian
- // package, this is required since the number of wireless bridges provided by
- // the debian package has gone from 10 down to 1, but our debian packages in
- // cloudtop are not up to date
- auto bridge_name = "cvd-wbr-01";
if (!CreateTap(name)) {
return false;
}
@@ -81,13 +77,13 @@
config.has_tap = true;
if (!LinkTapToBridge(name, bridge_name)) {
- CleanupWirelessIface(name, config);
+ CleanupEthernetIface(name, config);
return false;
}
if (!has_ipv4_bridge) {
if (!CreateEbtables(name, true, use_ebtables_legacy)) {
- CleanupWirelessIface(name, config);
+ CleanupEthernetIface(name, config);
return false;
}
config.has_broute_ipv4 = true;
@@ -95,7 +91,7 @@
if (!has_ipv6_bridge) {
if (CreateEbtables(name, false, use_ebtables_legacy)) {
- CleanupWirelessIface(name, config);
+ CleanupEthernetIface(name, config);
return false;
}
config.has_broute_ipv6 = true;
@@ -183,7 +179,7 @@
return status == 0;
}
-bool DestroyWirelessIface(const std::string& name, bool has_ipv4_bridge,
+bool DestroyEthernetIface(const std::string& name, bool has_ipv4_bridge,
bool has_ipv6_bridge, bool use_ebtables_legacy) {
if (!has_ipv6_bridge) {
DestroyEbtables(name, false, use_ebtables_legacy);
@@ -196,8 +192,8 @@
return DestroyIface(name);
}
-void CleanupWirelessIface(const std::string& name,
- const WirelessNetworkConfig& config) {
+void CleanupEthernetIface(const std::string& name,
+ const EthernetNetworkConfig& config) {
if (config.has_broute_ipv6) {
DestroyEbtables(name, false, config.use_ebtables_legacy);
}
@@ -458,12 +454,13 @@
return status == 0;
}
-bool CreateWirelessBridgeIface(const std::string& name) {
+bool CreateEthernetBridgeIface(const std::string& name,
+ const std::string& ipaddr) {
if (!CreateBridge(name)) {
return false;
}
- if (!SetupBridgeGateway(name, kWirelessIp)) {
+ if (!SetupBridgeGateway(name, ipaddr)) {
DestroyBridge(name);
return false;
}
@@ -471,12 +468,13 @@
return true;
}
-bool DestroyWirelessBridgeIface(const std::string& name) {
+bool DestroyEthernetBridgeIface(const std::string& name,
+ const std::string& ipaddr) {
GatewayConfig config{true, true, true};
// Don't need to check if removing some part of the config failed, we need to
// remove the entire interface, so just ignore any error until the end
- CleanupBridgeGateway(name, kWirelessIp, config);
+ CleanupBridgeGateway(name, ipaddr, config);
return DestroyBridge(name);
}
diff --git a/host/libs/allocd/alloc_utils.h b/host/libs/allocd/alloc_utils.h
index 5c1fd76..63f6d26 100644
--- a/host/libs/allocd/alloc_utils.h
+++ b/host/libs/allocd/alloc_utils.h
@@ -36,6 +36,8 @@
constexpr char kWirelessIp[] = "192.168.96";
// Mobile network prefix
constexpr char kMobileIp[] = "192.168.97";
+// Ethernet network prefix
+constexpr char kEthernetIp[] = "192.168.98";
// permission bits for socket
constexpr int kSocketMode = 0666;
@@ -46,7 +48,7 @@
constexpr uint32_t kMaxIfaceNameId = 63;
// struct for managing configuration state
-struct WirelessNetworkConfig {
+struct EthernetNetworkConfig {
bool has_broute_ipv4 = false;
bool has_broute_ipv6 = false;
bool has_tap = false;
@@ -89,12 +91,14 @@
bool DestroyMobileIface(const std::string& name, uint16_t id,
const std::string& ipaddr);
-bool CreateWirelessIface(const std::string& name, bool has_ipv4_bridge,
- bool has_ipv6_bridge, bool use_ebtables_legacy);
-bool DestroyWirelessIface(const std::string& name, bool has_ipv4_bridge,
- bool use_ipv6, bool use_ebtables_legacy);
-void CleanupWirelessIface(const std::string& name,
- const WirelessNetworkConfig& config);
+bool CreateEthernetIface(const std::string& name, const std::string& bridge_name,
+ bool has_ipv4_bridge, bool has_ipv6_bridge,
+ bool use_ebtables_legacy);
+bool DestroyEthernetIface(const std::string& name,
+ bool has_ipv4_bridge, bool use_ipv6,
+ bool use_ebtables_legacy);
+void CleanupEthernetIface(const std::string& name,
+ const EthernetNetworkConfig& config);
bool IptableConfig(const std::string& network, bool add);
@@ -105,8 +109,10 @@
void CleanupBridgeGateway(const std::string& name, const std::string& ipaddr,
const GatewayConfig& config);
-bool CreateWirelessBridgeIface(const std::string& name);
-bool DestroyWirelessBridgeIface(const std::string& name);
+bool CreateEthernetBridgeIface(const std::string& name,
+ const std::string &ipaddr);
+bool DestroyEthernetBridgeIface(const std::string& name,
+ const std::string &ipaddr);
bool AddGateway(const std::string& name, const std::string& gateway,
const std::string& netmask);
diff --git a/host/libs/allocd/request.h b/host/libs/allocd/request.h
index 8e1302d..1fbf6c9 100644
--- a/host/libs/allocd/request.h
+++ b/host/libs/allocd/request.h
@@ -45,7 +45,9 @@
Invalid = 0, // an invalid interface
mtap, // mobile tap
wtap, // wireless tap
- wbr // wireless bridge
+ etap, // ethernet tap
+ wbr, // wireless bridge
+ ebr // ethernet bridge
};
enum class RequestStatus : uint16_t {
diff --git a/host/libs/allocd/resource.cpp b/host/libs/allocd/resource.cpp
index 5a8b475..881dd85 100644
--- a/host/libs/allocd/resource.cpp
+++ b/host/libs/allocd/resource.cpp
@@ -30,13 +30,13 @@
return DestroyMobileIface(GetName(), iface_id_, ipaddr_);
}
-bool WirelessIface::AcquireResource() {
- return CreateWirelessIface(GetName(), has_ipv4_, has_ipv6_,
+bool EthernetIface::AcquireResource() {
+ return CreateEthernetIface(GetName(), GetBridgeName(), has_ipv4_, has_ipv6_,
use_ebtables_legacy_);
}
-bool WirelessIface::ReleaseResource() {
- return DestroyWirelessIface(GetName(), has_ipv4_, has_ipv6_,
+bool EthernetIface::ReleaseResource() {
+ return DestroyEthernetIface(GetName(), has_ipv4_, has_ipv6_,
use_ebtables_legacy_);
}
diff --git a/host/libs/allocd/resource.h b/host/libs/allocd/resource.h
index a92d38b..c30e9cc 100644
--- a/host/libs/allocd/resource.h
+++ b/host/libs/allocd/resource.h
@@ -26,8 +26,8 @@
enum class ResourceType {
Invalid = 0,
MobileIface,
- WirelessIface,
- WirelessBridge
+ EthernetIface,
+ EthernetBridge,
};
class StaticResource {
@@ -75,21 +75,25 @@
std::string ipaddr_;
};
-class WirelessIface : public StaticResource {
+class EthernetIface : public StaticResource {
public:
- WirelessIface() = default;
- ~WirelessIface() = default;
+ EthernetIface() = default;
+ ~EthernetIface() = default;
- WirelessIface(const std::string& name, uid_t uid, uint16_t iface_id,
- uint32_t global_id, std::string ipaddr)
+ EthernetIface(const std::string& name, uid_t uid, uint16_t iface_id,
+ uint32_t global_id, std::string bridge_name,
+ std::string ipaddr)
: StaticResource(name, uid, ResourceType::MobileIface, global_id),
iface_id_(iface_id),
+ bridge_name_(bridge_name),
ipaddr_(ipaddr) {}
bool ReleaseResource() override;
bool AcquireResource() override;
uint16_t GetIfaceId() { return iface_id_; }
+
+ std::string GetBridgeName() { return bridge_name_; }
std::string GetIpAddr() { return ipaddr_; }
void SetHasIpv4(bool ipv4) { has_ipv4_ = ipv4; }
@@ -105,6 +109,7 @@
private:
static constexpr char kNetmask[] = "/24";
uint16_t iface_id_;
+ std::string bridge_name_;
std::string ipaddr_;
bool has_ipv4_ = true;
bool has_ipv6_ = true;
diff --git a/host/libs/allocd/resource_manager.cpp b/host/libs/allocd/resource_manager.cpp
index 1ec6ac1..aaa8fe0 100644
--- a/host/libs/allocd/resource_manager.cpp
+++ b/host/libs/allocd/resource_manager.cpp
@@ -85,16 +85,20 @@
const char* idp = iface.c_str() + (iface.size() - 3);
int small_id = atoi(idp);
switch (ty) {
- case IfaceType::mtap: {
+ case IfaceType::mtap:
res = std::make_shared<MobileIface>(iface, uid, small_id, resource_id,
kMobileIp);
allocatedIface = res->AcquireResource();
pending_add_.insert({resource_id, res});
break;
- }
case IfaceType::wtap: {
- auto w = std::make_shared<WirelessIface>(iface, uid, small_id,
- resource_id, kMobileIp);
+ // TODO (paulkirth): change this to cvd-wbr, to test w/ today's
+ // debian package, this is required since the number of wireless
+ // bridges provided by the debian package has gone from 10 down to
+ // 1, but our debian packages in cloudtop are not up to date
+ auto w = std::make_shared<EthernetIface>(iface, uid, small_id,
+ resource_id, "cvd-wbr-01",
+ kWirelessIp);
w->SetUseEbtablesLegacy(use_ebtables_legacy_);
w->SetHasIpv4(use_ipv4_bridge_);
w->SetHasIpv6(use_ipv6_bridge_);
@@ -103,10 +107,22 @@
pending_add_.insert({resource_id, res});
break;
}
- case IfaceType::wbr: {
- allocatedIface = CreateBridge(iface);
+ case IfaceType::etap: {
+ auto w = std::make_shared<EthernetIface>(iface, uid, small_id,
+ resource_id, "cvd-ebr",
+ kEthernetIp);
+ w->SetUseEbtablesLegacy(use_ebtables_legacy_);
+ w->SetHasIpv4(use_ipv4_bridge_);
+ w->SetHasIpv6(use_ipv6_bridge_);
+ res = w;
+ allocatedIface = res->AcquireResource();
+ pending_add_.insert({resource_id, res});
break;
}
+ case IfaceType::wbr:
+ case IfaceType::ebr:
+ allocatedIface = CreateBridge(iface);
+ break;
case IfaceType::Invalid:
break;
}
@@ -138,15 +154,15 @@
removedIface = DestroyMobileIface(iface, id, kMobileIp);
break;
}
- case IfaceType::wtap: {
- removedIface = DestroyWirelessIface(
+ case IfaceType::wtap:
+ case IfaceType::etap:
+ removedIface = DestroyEthernetIface(
iface, use_ipv4_bridge_, use_ipv6_bridge_, use_ebtables_legacy_);
break;
- }
- case IfaceType::wbr: {
+ case IfaceType::wbr:
+ case IfaceType::ebr:
removedIface = DestroyBridge(iface);
break;
- }
case IfaceType::Invalid:
break;
}
diff --git a/host/libs/allocd/utils.cpp b/host/libs/allocd/utils.cpp
index 603c8d0..c60dc0c 100644
--- a/host/libs/allocd/utils.cpp
+++ b/host/libs/allocd/utils.cpp
@@ -58,13 +58,17 @@
{"invalid", IfaceType::Invalid},
{"mtap", IfaceType::mtap},
{"wtap", IfaceType::wtap},
- {"wbr", IfaceType::wbr}};
+ {"etap", IfaceType::etap},
+ {"wbr", IfaceType::wbr},
+ {"ebr", IfaceType::ebr}};
const std::map<IfaceType, std::string> IfaceTyToStrMap = {
{IfaceType::Invalid, "invalid"},
{IfaceType::mtap, "mtap"},
{IfaceType::wtap, "wtap"},
- {IfaceType::wbr, "wbr"}};
+ {IfaceType::etap, "etap"},
+ {IfaceType::wbr, "wbr"},
+ {IfaceType::ebr, "ebr"}};
const std::map<RequestStatus, std::string> ReqStatusToStrMap = {
{RequestStatus::Invalid, "invalid"},
@@ -167,8 +171,12 @@
return "mtap";
case IfaceType::wtap:
return "wtap";
+ case IfaceType::etap:
+ return "etap";
case IfaceType::wbr:
return "wbr";
+ case IfaceType::ebr:
+ return "ebr";
}
}
diff --git a/host/libs/config/Android.bp b/host/libs/config/Android.bp
index 53db489..152a0de 100644
--- a/host/libs/config/Android.bp
+++ b/host/libs/config/Android.bp
@@ -16,6 +16,7 @@
cc_library_static {
name: "libcuttlefish_host_config",
srcs: [
+ "custom_actions.cpp",
"cuttlefish_config.cpp",
"cuttlefish_config_instance.cpp",
"data_image.cpp",
diff --git a/host/libs/config/custom_actions.cpp b/host/libs/config/custom_actions.cpp
new file mode 100644
index 0000000..2c78eba
--- /dev/null
+++ b/host/libs/config/custom_actions.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host/libs/config/custom_actions.h"
+
+#include <android-base/logging.h>
+#include <json/json.h>
+
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "host/libs/config/cuttlefish_config.h"
+
+namespace cuttlefish {
+namespace {
+
+const char* kCustomActionShellCommand = "shell_command";
+const char* kCustomActionServer = "server";
+const char* kCustomActionButton = "button";
+const char* kCustomActionButtons = "buttons";
+const char* kCustomActionButtonCommand = "command";
+const char* kCustomActionButtonTitle = "title";
+const char* kCustomActionButtonIconName = "icon_name";
+
+} //namespace
+
+
+CustomActionConfig::CustomActionConfig(const Json::Value& dictionary) {
+ 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(),
+ button_entry[kCustomActionButtonTitle].asString(),
+ button_entry[kCustomActionButtonIconName].asString()}};
+ shell_command = dictionary[kCustomActionShellCommand].asString();
+ } else if (dictionary.isMember(kCustomActionServer)) {
+ // Action server with possibly multiple buttons.
+ for (const Json::Value& button_entry : dictionary[kCustomActionButtons]) {
+ ControlPanelButton button = {
+ button_entry[kCustomActionButtonCommand].asString(),
+ button_entry[kCustomActionButtonTitle].asString(),
+ button_entry[kCustomActionButtonIconName].asString()};
+ buttons.push_back(button);
+ }
+ server = dictionary[kCustomActionServer].asString();
+ } else {
+ LOG(ERROR) << "Unknown custom action format.";
+ }
+}
+
+Json::Value CustomActionConfig::ToJson() const {
+ Json::Value custom_action;
+ if (shell_command) {
+ // Shell command with one button.
+ custom_action[kCustomActionShellCommand] = *shell_command;
+ custom_action[kCustomActionButton] = Json::Value();
+ custom_action[kCustomActionButton][kCustomActionButtonCommand] =
+ buttons[0].command;
+ custom_action[kCustomActionButton][kCustomActionButtonTitle] =
+ buttons[0].title;
+ custom_action[kCustomActionButton][kCustomActionButtonIconName] =
+ buttons[0].icon_name;
+ } else if (server) {
+ // Action server with possibly multiple buttons.
+ custom_action[kCustomActionServer] = *server;
+ custom_action[kCustomActionButtons] = Json::Value(Json::arrayValue);
+ for (const auto& button : buttons) {
+ Json::Value button_entry;
+ button_entry[kCustomActionButtonCommand] = button.command;
+ button_entry[kCustomActionButtonTitle] = button.title;
+ button_entry[kCustomActionButtonIconName] = button.icon_name;
+ custom_action[kCustomActionButtons].append(button_entry);
+ }
+ } else {
+ LOG(ERROR) << "Unknown custom action type.";
+ }
+ return custom_action;
+}
+
+} // namespace cuttlefish
diff --git a/host/libs/config/custom_actions.h b/host/libs/config/custom_actions.h
new file mode 100644
index 0000000..51e73ba
--- /dev/null
+++ b/host/libs/config/custom_actions.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <json/json.h>
+
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace cuttlefish {
+
+struct ControlPanelButton {
+ std::string command;
+ std::string title;
+ std::string icon_name;
+};
+
+struct CustomActionConfig {
+ CustomActionConfig(const Json::Value&);
+ Json::Value ToJson() const;
+
+ std::vector<ControlPanelButton> buttons;
+ std::optional<std::string> shell_command;
+ std::optional<std::string> server;
+};
+
+} // namespace cuttlefish
diff --git a/host/libs/config/cuttlefish_config.cpp b/host/libs/config/cuttlefish_config.cpp
index 8d9a645..1a50ff6 100644
--- a/host/libs/config/cuttlefish_config.cpp
+++ b/host/libs/config/cuttlefish_config.cpp
@@ -117,6 +117,8 @@
const char* kEnableVehicleHalServer = "enable_vehicle_hal_server";
const char* kVehicleHalServerBinary = "vehicle_hal_server_binary";
+const char* kCustomActions = "custom_actions";
+
const char* kRestartSubprocesses = "restart_subprocesses";
const char* kRunAdbConnector = "run_adb_connector";
@@ -166,6 +168,8 @@
const char* kVhostNet = "vhost_net";
+const char* kEthernet = "ethernet";
+
} // namespace
const char* const kGpuModeAuto = "auto";
@@ -448,6 +452,22 @@
return (*dictionary_)[kVehicleHalServerBinary].asString();
}
+void CuttlefishConfig::set_custom_actions(const std::vector<CustomActionConfig>& actions) {
+ Json::Value actions_array(Json::arrayValue);
+ for (const auto& action : actions) {
+ actions_array.append(action.ToJson());
+ }
+ (*dictionary_)[kCustomActions] = actions_array;
+}
+
+std::vector<CustomActionConfig> CuttlefishConfig::custom_actions() const {
+ std::vector<CustomActionConfig> result;
+ for (Json::Value custom_action : (*dictionary_)[kCustomActions]) {
+ result.push_back(CustomActionConfig(custom_action));
+ }
+ return result;
+}
+
void CuttlefishConfig::set_webrtc_assets_dir(const std::string& webrtc_assets_dir) {
(*dictionary_)[kWebRTCAssetsDir] = webrtc_assets_dir;
}
@@ -775,6 +795,13 @@
return (*dictionary_)[kVhostNet].asBool();
}
+void CuttlefishConfig::set_ethernet(bool ethernet) {
+ (*dictionary_)[kEthernet] = ethernet;
+}
+bool CuttlefishConfig::ethernet() const {
+ return (*dictionary_)[kEthernet].asBool();
+}
+
// Creates the (initially empty) config object and populates it with values from
// the config file if the CUTTLEFISH_CONFIG_FILE env variable is present.
// Returns nullptr if there was an error loading from file
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index bccc3ab..a199728 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -20,10 +20,13 @@
#include <cstdint>
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include <set>
#include <vector>
+#include "host/libs/config/custom_actions.h"
+
namespace Json {
class Value;
}
@@ -45,6 +48,8 @@
"VIRTUAL_DEVICE_NETWORK_MOBILE_CONNECTED";
constexpr char kWifiConnectedMessage[] =
"VIRTUAL_DEVICE_NETWORK_WIFI_CONNECTED";
+constexpr char kEthernetConnectedMessage[] =
+ "VIRTUAL_DEVICE_NETWORK_ETHERNET_CONNECTED";
constexpr char kScreenChangedMessage[] = "VIRTUAL_DEVICE_SCREEN_CHANGED";
constexpr char kInternalDirName[] = "internal";
constexpr char kSharedDirName[] = "shared";
@@ -189,6 +194,9 @@
void set_vehicle_hal_grpc_server_binary(const std::string& vhal_server_binary);
std::string vehicle_hal_grpc_server_binary() const;
+ void set_custom_actions(const std::vector<CustomActionConfig>& actions);
+ std::vector<CustomActionConfig> custom_actions() const;
+
void set_restart_subprocesses(bool restart_subprocesses);
bool restart_subprocesses() const;
@@ -313,6 +321,9 @@
void set_vhost_net(bool vhost_net);
bool vhost_net() const;
+ void set_ethernet(bool ethernet);
+ bool ethernet() const;
+
class InstanceSpecific;
class MutableInstanceSpecific;
@@ -369,6 +380,7 @@
std::string mobile_bridge_name() const;
std::string mobile_tap_name() const;
std::string wifi_tap_name() const;
+ std::string ethernet_tap_name() const;
uint32_t session_id() const;
bool use_allocd() const;
int vsock_guest_cid() const;
@@ -461,6 +473,7 @@
void set_mobile_bridge_name(const std::string& mobile_bridge_name);
void set_mobile_tap_name(const std::string& mobile_tap_name);
void set_wifi_tap_name(const std::string& wifi_tap_name);
+ void set_ethernet_tap_name(const std::string& ethernet_tap_name);
void set_session_id(uint32_t session_id);
void set_use_allocd(bool use_allocd);
void set_vsock_guest_cid(int vsock_guest_cid);
diff --git a/host/libs/config/cuttlefish_config_instance.cpp b/host/libs/config/cuttlefish_config_instance.cpp
index 8bb4c84..d0f919e 100644
--- a/host/libs/config/cuttlefish_config_instance.cpp
+++ b/host/libs/config/cuttlefish_config_instance.cpp
@@ -34,6 +34,7 @@
const char* kMobileBridgeName = "mobile_bridge_name";
const char* kMobileTapName = "mobile_tap_name";
const char* kWifiTapName = "wifi_tap_name";
+const char* kEthernetTapName = "ethernet_tap_name";
const char* kVsockGuestCid = "vsock_guest_cid";
const char* kSessionId = "session_id";
@@ -220,6 +221,14 @@
(*Dictionary())[kWifiTapName] = wifi_tap_name;
}
+std::string CuttlefishConfig::InstanceSpecific::ethernet_tap_name() const {
+ return (*Dictionary())[kEthernetTapName].asString();
+}
+void CuttlefishConfig::MutableInstanceSpecific::set_ethernet_tap_name(
+ const std::string& ethernet_tap_name) {
+ (*Dictionary())[kEthernetTapName] = ethernet_tap_name;
+}
+
bool CuttlefishConfig::InstanceSpecific::use_allocd() const {
return (*Dictionary())[kUseAllocd].asBool();
}
diff --git a/host/libs/screen_connector/screen_connector.cpp b/host/libs/screen_connector/screen_connector.cpp
index 91bd0c4..a7d28ca 100644
--- a/host/libs/screen_connector/screen_connector.cpp
+++ b/host/libs/screen_connector/screen_connector.cpp
@@ -37,4 +37,7 @@
}
}
+// Ignore by default
+void ScreenConnector::ReportClientsConnected(bool /*have_clients*/) {}
+
} // namespace cuttlefish
diff --git a/host/libs/screen_connector/screen_connector.h b/host/libs/screen_connector/screen_connector.h
index 68df5eb..2768cca 100644
--- a/host/libs/screen_connector/screen_connector.h
+++ b/host/libs/screen_connector/screen_connector.h
@@ -38,6 +38,9 @@
virtual bool OnFrameAfter(std::uint32_t frame_number,
const FrameCallback& frame_callback) = 0;
+ // Let the screen connector know when there are clients connected
+ virtual void ReportClientsConnected(bool have_clients);
+
static inline constexpr int BytesPerPixel() {
return sizeof(int32_t);
}
@@ -62,4 +65,4 @@
ScreenConnector() = default;
};
-} // namespace cuttlefish
\ No newline at end of file
+} // namespace cuttlefish
diff --git a/host/libs/screen_connector/socket_based_screen_connector.cpp b/host/libs/screen_connector/socket_based_screen_connector.cpp
index d7d2168..a0498b5 100644
--- a/host/libs/screen_connector/socket_based_screen_connector.cpp
+++ b/host/libs/screen_connector/socket_based_screen_connector.cpp
@@ -66,23 +66,24 @@
while (1) {
LOG(DEBUG) << "Screen Connector accepting connections...";
- auto conn = SharedFD::Accept(*server);
- if (!conn->IsOpen()) {
+ client_connection_ = SharedFD::Accept(*server);
+ if (!client_connection_->IsOpen()) {
LOG(ERROR) << "Disconnected fd returned from accept";
continue;
}
- while (conn->IsOpen()) {
+ ReportClientsConnected(have_clients_);
+ while (client_connection_->IsOpen()) {
int32_t size = 0;
- if (conn->Read(&size, sizeof(size)) < 0) {
- LOG(ERROR) << "Failed to read from hwcomposer: " << conn->StrError();
+ if (client_connection_->Read(&size, sizeof(size)) < 0) {
+ LOG(ERROR) << "Failed to read from hwcomposer: " << client_connection_->StrError();
break;
}
auto buff = reinterpret_cast<uint8_t*>(GetBuffer(current_buffer));
while (size > 0) {
- auto read = conn->Read(buff, size);
+ auto read = client_connection_->Read(buff, size);
if (read < 0) {
- LOG(ERROR) << "Failed to read from hwcomposer: " << conn->StrError();
- conn->Close();
+ LOG(ERROR) << "Failed to read from hwcomposer: " << client_connection_->StrError();
+ client_connection_->Close();
break;
}
size -= read;
@@ -94,6 +95,12 @@
}
}
+void SocketBasedScreenConnector::ReportClientsConnected(bool have_clients) {
+ have_clients_ = have_clients;
+ char buffer = have_clients ? 1 : 0;
+ (void)client_connection_->Write(&buffer, sizeof(buffer));
+}
+
void SocketBasedScreenConnector::BroadcastNewFrame(int buffer_idx) {
{
std::lock_guard<std::mutex> lock(new_frame_mtx_);
diff --git a/host/libs/screen_connector/socket_based_screen_connector.h b/host/libs/screen_connector/socket_based_screen_connector.h
index 1163e85..b43eabf 100644
--- a/host/libs/screen_connector/socket_based_screen_connector.h
+++ b/host/libs/screen_connector/socket_based_screen_connector.h
@@ -25,6 +25,8 @@
#include <thread>
#include <vector>
+#include "common/libs/fs/shared_fd.h"
+
namespace cuttlefish {
class SocketBasedScreenConnector : public ScreenConnector {
@@ -34,6 +36,8 @@
bool OnFrameAfter(std::uint32_t frame_number,
const FrameCallback& frame_callback) override;
+ void ReportClientsConnected(bool have_clients) override;
+
private:
static constexpr int NUM_BUFFERS_ = 4;
@@ -49,6 +53,8 @@
std::condition_variable new_frame_cond_var_;
std::mutex new_frame_mtx_;
std::thread screen_server_thread_;
+ cuttlefish::SharedFD client_connection_;
+ bool have_clients_ = false;
};
-} // namespace cuttlefish
\ No newline at end of file
+} // namespace cuttlefish
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index b067096..5d86aac 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -286,6 +286,12 @@
"path=", instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
",input=", instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"));
+ // TODO(b/172286896): This is temporarily optional, but should be made
+ // unconditional and moved up to the other network devices area
+ if (config.ethernet()) {
+ AddTapFdParameter(&crosvm_cmd, instance.ethernet_tap_name());
+ }
+
// TODO(b/162071003): virtiofs crashes without sandboxing, this should be fixed
if (config.enable_sandbox()) {
// Set up directory shared with virtiofs
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index fabe406..fd40bf3 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -411,6 +411,17 @@
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("AC97");
+ // TODO(b/172286896): This is temporarily optional, but should be made
+ // unconditional and moved up to the other network devices area
+ if (config.ethernet()) {
+ qemu_cmd.AddParameter("-netdev");
+ qemu_cmd.AddParameter("tap,id=hostnet2,ifname=", instance.ethernet_tap_name(),
+ ",script=no,downscript=no", vhost_net);
+
+ qemu_cmd.AddParameter("-device");
+ qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet2,id=net2");
+ }
+
if (config.use_bootloader()) {
qemu_cmd.AddParameter("-bios");
qemu_cmd.AddParameter(config.bootloader());
diff --git a/tests/recovery/Android.bp b/tests/recovery/Android.bp
new file mode 100644
index 0000000..05207ea
--- /dev/null
+++ b/tests/recovery/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+ name: "RebootRecoveryTest",
+ srcs: [
+ "src/**/*.java",
+ ],
+ test_suites: [
+ "device-tests",
+ ],
+ libs: [
+ "tradefed",
+ ],
+}
diff --git a/tests/recovery/AndroidTest.xml b/tests/recovery/AndroidTest.xml
new file mode 100644
index 0000000..f21b659
--- /dev/null
+++ b/tests/recovery/AndroidTest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<configuration description="GKI Install test">
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="jar" value="RebootRecoveryTest.jar" />
+ </test>
+</configuration>
diff --git a/tests/recovery/src/com/android/cuttlefish/tests/RebootRecoveryTest.java b/tests/recovery/src/com/android/cuttlefish/tests/RebootRecoveryTest.java
new file mode 100644
index 0000000..f4d7757
--- /dev/null
+++ b/tests/recovery/src/com/android/cuttlefish/tests/RebootRecoveryTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cuttlefish.tests;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test rebooting into recovery.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class RebootRecoveryTest extends BaseHostJUnit4Test {
+ @Test
+ public void testRebootRecovery() throws Exception {
+ getDevice().rebootIntoRecovery();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ getDevice().reboot();
+ }
+}