blob: fcdfb3ff2a4b41e3b37cc6a48ac8d58deef9f582 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/services/libassistant/util.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/values.h"
#include "build/util/chromium_git_revision.h"
#include "chromeos/assistant/buildflags.h"
#include "chromeos/assistant/internal/internal_constants.h"
#include "chromeos/assistant/internal/internal_util.h"
#include "chromeos/assistant/internal/util_headers.h"
#include "chromeos/dbus/util/version_loader.h"
#include "chromeos/services/assistant/public/cpp/features.h"
#include "chromeos/services/libassistant/constants.h"
#include "chromeos/services/libassistant/public/cpp/android_app_info.h"
using ::assistant::api::Interaction;
using chromeos::assistant::shared::ClientInteraction;
using chromeos::assistant::shared::ClientOpResult;
using chromeos::assistant::shared::GetDeviceSettingsResult;
using chromeos::assistant::shared::Protobuf;
using chromeos::assistant::shared::ProviderVerificationResult;
using chromeos::assistant::shared::ResponseCode;
using chromeos::assistant::shared::SettingInfo;
using chromeos::assistant::shared::VerifyProviderClientOpResult;
namespace chromeos {
namespace libassistant {
namespace {
using AppStatus = ::chromeos::assistant::AppStatus;
void CreateUserAgent(std::string* user_agent) {
DCHECK(user_agent->empty());
base::StringAppendF(user_agent,
"Mozilla/5.0 (X11; CrOS %s %s; %s) "
"AppleWebKit/537.36 (KHTML, like Gecko)",
base::SysInfo::OperatingSystemArchitecture().c_str(),
base::SysInfo::OperatingSystemVersion().c_str(),
base::SysInfo::GetLsbReleaseBoard().c_str());
std::string arc_version = chromeos::version_loader::GetARCVersion();
if (!arc_version.empty())
base::StringAppendF(user_agent, " ARC/%s", arc_version.c_str());
}
ProviderVerificationResult::VerificationStatus GetProviderVerificationStatus(
AppStatus status) {
switch (status) {
case AppStatus::kUnknown:
return ProviderVerificationResult::UNKNOWN;
case AppStatus::kAvailable:
return ProviderVerificationResult::AVAILABLE;
case AppStatus::kUnavailable:
return ProviderVerificationResult::UNAVAILABLE;
case AppStatus::kVersionMismatch:
return ProviderVerificationResult::VERSION_MISMATCH;
case AppStatus::kDisabled:
return ProviderVerificationResult::DISABLED;
}
}
SettingInfo ToSettingInfo(bool is_supported) {
SettingInfo result;
result.set_available(is_supported);
result.set_setting_status(is_supported
? SettingInfo::AVAILABLE_AND_MODIFY_SUPPORTED
: SettingInfo::UNAVAILABLE);
return result;
}
// Helper class used for constructing V1 interaction proto messages.
class V1InteractionBuilder {
public:
V1InteractionBuilder() = default;
V1InteractionBuilder(V1InteractionBuilder&) = delete;
V1InteractionBuilder& operator=(V1InteractionBuilder&) = delete;
~V1InteractionBuilder() = default;
V1InteractionBuilder& SetInResponseTo(int interaction_id) {
interaction_.set_in_response_to(interaction_id);
return *this;
}
V1InteractionBuilder& AddResult(
const std::string& key,
const google::protobuf::MessageLite& result_proto) {
auto* result = client_op_result()->mutable_results()->add_result();
result->set_key(key);
result->mutable_value()->set_protobuf_type(result_proto.GetTypeName());
result->mutable_value()->set_protobuf_data(
result_proto.SerializeAsString());
return *this;
}
V1InteractionBuilder& SetStatusCode(ResponseCode::Status status_code) {
ResponseCode* response_code = client_op_result()->mutable_response_code();
response_code->set_status_code(status_code);
return *this;
}
// Set the status code to |OK| (if true) or |NOT_FOUND| (if false).
V1InteractionBuilder& SetStatusCodeFromEntityFound(bool found) {
SetStatusCode(found ? ResponseCode::OK : ResponseCode::NOT_FOUND);
return *this;
}
V1InteractionBuilder& SetClientInputName(const std::string& name) {
auto* client_input = client_interaction()->mutable_client_input();
client_input->set_client_input_name(name);
return *this;
}
V1InteractionBuilder& AddClientInputParams(
const std::string& key,
const google::protobuf::MessageLite& params_proto) {
auto* client_input = client_interaction()->mutable_client_input();
Protobuf& value = (*client_input->mutable_params())[key];
value.set_protobuf_type(params_proto.GetTypeName());
value.set_protobuf_data(params_proto.SerializeAsString());
return *this;
}
std::string SerializeAsString() { return interaction_.SerializeAsString(); }
Interaction Proto() { return interaction_; }
private:
ClientInteraction* client_interaction() {
return interaction_.mutable_from_client();
}
ClientOpResult* client_op_result() {
return client_interaction()->mutable_client_op_result();
}
Interaction interaction_;
};
bool ShouldPutLogsInHomeDirectory() {
// Redirects libassistant logging to /var/log/chrome/. This is mainly used to
// help collect logs when running tests.
constexpr char kRedirectLibassistantLogging[] =
"redirect-libassistant-logging";
const bool redirect_logging =
base::CommandLine::ForCurrentProcess()->HasSwitch(
kRedirectLibassistantLogging);
return !redirect_logging;
}
bool ShouldLogToFile() {
// Redirects libassistant logging to stdout. This is mainly used to help test
// locally.
constexpr char kDisableLibAssistantLogfile[] = "disable-libassistant-logfile";
const bool disable_logfile =
base::CommandLine::ForCurrentProcess()->HasSwitch(
kDisableLibAssistantLogfile);
return !disable_logfile;
}
} // namespace
base::FilePath GetBaseAssistantDir() {
if (base::SysInfo::IsRunningOnChromeOS())
return base::FilePath(FILE_PATH_LITERAL(kAssistantBaseDirPath));
return base::FilePath(FILE_PATH_LITERAL(kAssistantTempBaseDirPath));
}
std::string CreateLibAssistantConfig(
absl::optional<std::string> s3_server_uri_override,
absl::optional<std::string> device_id_override) {
using Value = base::Value;
using Type = base::Value::Type;
Value config(Type::DICTIONARY);
Value device(Type::DICTIONARY);
device.SetKey("board_name", Value(base::SysInfo::GetLsbReleaseBoard()));
device.SetKey("board_revision", Value("1"));
device.SetKey("embedder_build_info",
Value(chromeos::version_loader::GetVersion(
chromeos::version_loader::VERSION_FULL)));
device.SetKey("model_id", Value(assistant::kModelId));
device.SetKey("model_revision", Value(1));
config.SetKey("device", std::move(device));
// Enables Libassistant gRPC server for V2.
if (chromeos::assistant::features::IsLibAssistantV2Enabled()) {
Value libas_server(Type::DICTIONARY);
libas_server.SetKey("libas_server_address",
Value(assistant::kLibassistantServiceAddress));
config.SetKey("libas_server", std::move(libas_server));
}
Value discovery(Type::DICTIONARY);
discovery.SetKey("enable_mdns", Value(false));
config.SetKey("discovery", std::move(discovery));
Value internal(Type::DICTIONARY);
internal.SetKey("surface_type", Value("OPA_CROS"));
std::string user_agent;
CreateUserAgent(&user_agent);
internal.SetKey("user_agent", Value(user_agent));
// Prevent LibAssistant from automatically playing ready message TTS during
// the startup sequence when the version of LibAssistant has been upgraded.
internal.SetKey("override_ready_message", Value(true));
// Set DeviceProperties.visibility to Visibility::PRIVATE.
// See //libassistant/shared/proto/device_properties.proto.
internal.SetKey("visibility", Value("PRIVATE"));
if (ShouldLogToFile()) {
Value logging(Type::DICTIONARY);
std::string log_dir("/var/log/chrome/");
if (ShouldPutLogsInHomeDirectory()) {
base::FilePath log_path =
GetBaseAssistantDir().Append(FILE_PATH_LITERAL("log"));
// The directory will be created by LibassistantPreSandboxHook if sandbox
// is enabled.
if (!assistant::features::IsLibAssistantSandboxEnabled())
CHECK(base::CreateDirectory(log_path));
log_dir = log_path.value();
}
logging.SetKey("directory", Value(log_dir));
// Maximum disk space consumed by all log files. There are 5 rotating log
// files on disk.
logging.SetKey("max_size_kb", Value(3 * 1024));
// Empty "output_type" disables logging to stderr.
logging.SetKey("output_type", Value(Type::LIST));
config.SetKey("logging", std::move(logging));
} else {
// Print logs to console if running in desktop mode.
internal.SetKey("disable_log_files", Value(true));
}
// Enable logging.
internal.SetBoolKey("enable_logging", true);
// This only enables logging to local disk combined with the flag above. When
// user choose to file a Feedback report, user can examine the log and choose
// to upload the log with the report or not.
internal.SetBoolKey("logging_opt_in", true);
// Allows libassistant to automatically toggle signed-out mode depending on
// whether it has auth_tokens.
internal.SetBoolKey("enable_signed_out_mode", true);
config.SetKey("internal", std::move(internal));
Value audio_input(Type::DICTIONARY);
// Skip sending speaker ID selection info to disable user verification.
audio_input.SetKey("should_send_speaker_id_selection_info", Value(false));
Value sources(Type::LIST);
Value dict(Type::DICTIONARY);
dict.SetKey("enable_eraser",
Value(assistant::features::IsAudioEraserEnabled()));
dict.SetKey("enable_eraser_toggling",
Value(assistant::features::IsAudioEraserEnabled()));
sources.Append(std::move(dict));
audio_input.SetKey("sources", std::move(sources));
config.SetKey("audio_input", std::move(audio_input));
if (assistant::features::IsLibAssistantBetaBackendEnabled())
config.SetStringPath("internal.backend_type", "BETA_DOGFOOD");
// Use http unless we're using the fake s3 server, which requires grpc.
if (s3_server_uri_override)
config.SetStringPath("internal.transport_type", "GRPC");
else
config.SetStringPath("internal.transport_type", "HTTP");
if (device_id_override)
config.SetStringPath("internal.cast_device_id", device_id_override.value());
config.SetBoolPath("internal.enable_on_device_assistant_tts_as_text", true);
// Finally add in the server uri override.
if (s3_server_uri_override) {
config.SetStringPath("testing.s3_grpc_server_uri",
s3_server_uri_override.value());
}
std::string json;
base::JSONWriter::Write(config, &json);
return json;
}
Interaction CreateVerifyProviderResponseInteraction(
const int interaction_id,
const std::vector<chromeos::assistant::AndroidAppInfo>& apps_info) {
// Construct verify provider result proto.
VerifyProviderClientOpResult result_proto;
bool any_provider_available = false;
for (const auto& android_app_info : apps_info) {
auto* provider_status = result_proto.add_provider_status();
provider_status->set_status(
GetProviderVerificationStatus(android_app_info.status));
auto* app_info =
provider_status->mutable_provider_info()->mutable_android_app_info();
app_info->set_package_name(android_app_info.package_name);
app_info->set_app_version(android_app_info.version);
app_info->set_localized_app_name(android_app_info.localized_app_name);
app_info->set_android_intent(android_app_info.intent);
if (android_app_info.status == AppStatus::kAvailable)
any_provider_available = true;
}
// Construct response interaction.
return V1InteractionBuilder()
.SetInResponseTo(interaction_id)
.SetStatusCodeFromEntityFound(any_provider_available)
.AddResult(assistant::kResultKeyVerifyProvider, result_proto)
.Proto();
}
Interaction CreateGetDeviceSettingInteraction(
int interaction_id,
const std::vector<chromeos::assistant::DeviceSetting>& device_settings) {
GetDeviceSettingsResult result_proto;
for (const auto& setting : device_settings) {
(*result_proto.mutable_settings_info())[setting.setting_id] =
ToSettingInfo(setting.is_supported);
}
// Construct response interaction.
return V1InteractionBuilder()
.SetInResponseTo(interaction_id)
.SetStatusCode(ResponseCode::OK)
.AddResult(/*key=*/assistant::kResultKeyGetDeviceSettings, result_proto)
.Proto();
}
Interaction CreateNotificationRequestInteraction(
const std::string& notification_id,
const std::string& consistent_token,
const std::string& opaque_token,
const int action_index) {
auto request_param = assistant::CreateNotificationRequestParam(
notification_id, consistent_token, opaque_token, action_index);
return V1InteractionBuilder()
.SetClientInputName(assistant::kClientInputRequestNotification)
.AddClientInputParams(assistant::kNotificationRequestParamsKey,
request_param)
.Proto();
}
Interaction CreateNotificationDismissedInteraction(
const std::string& notification_id,
const std::string& consistent_token,
const std::string& opaque_token,
const std::vector<std::string>& grouping_keys) {
auto dismiss_param = assistant::CreateNotificationDismissedParam(
notification_id, consistent_token, opaque_token, grouping_keys);
return V1InteractionBuilder()
.SetClientInputName(assistant::kClientInputDismissNotification)
.AddClientInputParams(assistant::kNotificationDismissParamsKey,
dismiss_param)
.Proto();
}
Interaction CreateEditReminderInteraction(const std::string& reminder_id) {
auto intent_input = assistant::CreateEditReminderParam(reminder_id);
return V1InteractionBuilder()
.SetClientInputName(assistant::kClientInputEditReminder)
.AddClientInputParams(assistant::kEditReminderParamsKey, intent_input)
.Proto();
}
Interaction CreateOpenProviderResponseInteraction(const int interaction_id,
const bool provider_found) {
return V1InteractionBuilder()
.SetInResponseTo(interaction_id)
.SetStatusCodeFromEntityFound(provider_found)
.Proto();
}
Interaction CreateSendFeedbackInteraction(
bool assistant_debug_info_allowed,
const std::string& feedback_description,
const std::string& screenshot_png) {
auto feedback_arg = assistant::CreateFeedbackParam(
assistant_debug_info_allowed, feedback_description, screenshot_png);
return V1InteractionBuilder()
.SetClientInputName(assistant::kClientInputText)
.AddClientInputParams(
assistant::kTextParamsKey,
assistant::CreateTextParam(assistant::kFeedbackText))
.AddClientInputParams(assistant::kFeedbackParamsKey, feedback_arg)
.Proto();
}
Interaction CreateTextQueryInteraction(const std::string& query) {
return V1InteractionBuilder()
.SetClientInputName(assistant::kClientInputText)
.AddClientInputParams(assistant::kTextParamsKey,
assistant::CreateTextParam(query))
.Proto();
}
} // namespace libassistant
} // namespace chromeos