blob: 7a99a00a6ff75ca9b5e259ae19efc7a1f099897e [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "peridot/bin/sessionmgr/user_intelligence_provider_impl.h"
#include <fuchsia/bluetooth/le/cpp/fidl.h>
#include <fuchsia/cobalt/cpp/fidl.h>
#include <fuchsia/maxwell/internal/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <lib/component/cpp/connect.h>
#include <lib/fxl/files/file.h>
#include <lib/fxl/functional/make_copyable.h>
#include "peridot/bin/basemgr/cobalt/cobalt.h"
#include "peridot/bin/sessionmgr/intelligence_services_impl.h"
namespace modular {
namespace {
constexpr char kUsageLogUrl[] =
"fuchsia-pkg://fuchsia.com/usage_log#meta/usage_log.cmx";
constexpr char kKronkUrl[] = "kronk";
constexpr char kStoryInfoAgentUrl[] =
"fuchsia-pkg://fuchsia.com/story_info#meta/story_info.cmx";
static constexpr modular::RateLimitedRetry::Threshold kSessionAgentRetryLimit =
{3, zx::sec(45)};
fuchsia::modular::ComponentScope CloneScope(
const fuchsia::modular::ComponentScope& scope) {
fuchsia::modular::ComponentScope result;
fidl::Clone(scope, &result);
return result;
}
} // namespace
template <class Interface>
UserIntelligenceProviderImpl::SessionAgentData::DeferredInterfaceRequest::
DeferredInterfaceRequest(fidl::InterfaceRequest<Interface> request)
: name(Interface::Name_), channel(request.TakeChannel()) {}
UserIntelligenceProviderImpl::SessionAgentData::SessionAgentData()
: restart(kSessionAgentRetryLimit) {}
template <class Interface>
void UserIntelligenceProviderImpl::SessionAgentData::
ConnectOrQueueServiceRequest(fidl::InterfaceRequest<Interface> request) {
if (services) {
component::ConnectToService(services.get(), std::move(request));
} else {
pending_service_requests.emplace_back(std::move(request));
}
}
UserIntelligenceProviderImpl::UserIntelligenceProviderImpl(
component::StartupContext* const context,
fidl::InterfaceHandle<fuchsia::modular::ContextEngine>
context_engine_handle,
fit::function<
void(fidl::InterfaceRequest<fuchsia::modular::VisibleStoriesProvider>)>
visible_stories_provider_connector,
fit::function<void(fidl::InterfaceRequest<fuchsia::modular::StoryProvider>)>
story_provider_connector,
fit::function<void(fidl::InterfaceRequest<fuchsia::modular::FocusProvider>)>
focus_provider_connector,
fit::function<void(fidl::InterfaceRequest<fuchsia::modular::PuppetMaster>)>
puppet_master_connector)
: context_(context),
visible_stories_provider_connector_(
std::move(visible_stories_provider_connector)),
story_provider_connector_(std::move(story_provider_connector)),
focus_provider_connector_(std::move(focus_provider_connector)),
puppet_master_connector_(std::move(puppet_master_connector)) {
context_engine_.Bind(std::move(context_engine_handle));
// Start dependent processes. We get some component-scope services from
// these processes.
StartSuggestionEngine();
}
void UserIntelligenceProviderImpl::GetComponentIntelligenceServices(
fuchsia::modular::ComponentScope scope,
fidl::InterfaceRequest<fuchsia::modular::IntelligenceServices> request) {
intelligence_services_bindings_.AddBinding(
std::make_unique<IntelligenceServicesImpl>(
std::move(scope), context_engine_.get(), suggestion_engine_.get()),
std::move(request));
}
void UserIntelligenceProviderImpl::GetSuggestionProvider(
fidl::InterfaceRequest<fuchsia::modular::SuggestionProvider> request) {
suggestion_services_.ConnectToService(std::move(request));
}
void UserIntelligenceProviderImpl::GetSpeechToText(
fidl::InterfaceRequest<fuchsia::speech::SpeechToText> request) {
auto it = session_agents_.find(kKronkUrl);
if (it != session_agents_.end()) {
it->second.ConnectOrQueueServiceRequest(std::move(request));
} else {
FXL_LOG(WARNING) << "No speech-to-text agent loaded";
}
}
void UserIntelligenceProviderImpl::StartAgents(
fidl::InterfaceHandle<fuchsia::modular::ComponentContext>
component_context_handle,
std::vector<std::string> session_agents,
std::vector<std::string> startup_agents) {
component_context_.Bind(std::move(component_context_handle));
FXL_LOG(INFO) << "Starting session_agents:";
for (const auto& agent : session_agents) {
FXL_LOG(INFO) << " " << agent;
StartSessionAgent(agent);
}
FXL_LOG(INFO) << "Starting startup_agents:";
for (const auto& agent : startup_agents) {
FXL_LOG(INFO) << " " << agent;
StartAgent(agent);
}
StartAgent(kStoryInfoAgentUrl);
}
void UserIntelligenceProviderImpl::GetServicesForAgent(
std::string url, GetServicesForAgentCallback callback) {
fuchsia::sys::ServiceList service_list;
agent_namespaces_.emplace_back(service_list.provider.NewRequest());
auto* agent_host = &agent_namespaces_.back();
service_list.names = AddAgentServices(url, agent_host);
callback(std::move(service_list));
}
void UserIntelligenceProviderImpl::StartSuggestionEngine() {
auto service_list = fuchsia::sys::ServiceList::New();
service_list->names.push_back(fuchsia::modular::ContextReader::Name_);
suggestion_engine_service_provider_
.AddService<fuchsia::modular::ContextReader>(
[this](
fidl::InterfaceRequest<fuchsia::modular::ContextReader> request) {
fuchsia::modular::ComponentScope scope;
scope.set_global_scope(fuchsia::modular::GlobalScope());
context_engine_->GetReader(std::move(scope), std::move(request));
});
service_list->names.push_back(fuchsia::modular::PuppetMaster::Name_);
suggestion_engine_service_provider_
.AddService<fuchsia::modular::PuppetMaster>(
[this](
fidl::InterfaceRequest<fuchsia::modular::PuppetMaster> request) {
puppet_master_connector_(std::move(request));
});
fuchsia::sys::ServiceProviderPtr service_provider;
suggestion_engine_service_provider_.AddBinding(service_provider.NewRequest());
service_list->provider = std::move(service_provider);
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = "fuchsia-pkg://fuchsia.com/suggestion_engine#meta/suggestion_engine.cmx";
launch_info.directory_request = suggestion_services_.NewRequest();
launch_info.additional_services = std::move(service_list);
context_->launcher()->CreateComponent(std::move(launch_info), nullptr);
suggestion_engine_ =
suggestion_services_
.ConnectToService<fuchsia::modular::SuggestionEngine>();
}
void UserIntelligenceProviderImpl::StartAgent(const std::string& url) {
fuchsia::modular::AgentControllerPtr controller;
fuchsia::sys::ServiceProviderPtr services;
component_context_->ConnectToAgent(url, services.NewRequest(),
controller.NewRequest());
agent_controllers_.push_back(std::move(controller));
}
void UserIntelligenceProviderImpl::StartSessionAgent(const std::string& url) {
SessionAgentData* const agent_data = &session_agents_[url];
component_context_->ConnectToAgent(url, agent_data->services.NewRequest(),
agent_data->controller.NewRequest());
// complete any pending connection requests
for (auto& request : agent_data->pending_service_requests) {
agent_data->services->ConnectToService(request.name,
std::move(request.channel));
}
agent_data->pending_service_requests.clear();
// fuchsia::modular::Agent runner closes the agent controller connection when
// the agent terminates. We restart the agent (up to a limit) when we notice
// this.
//
// NOTE(rosswang,mesch): Although the interface we're actually interested in
// is |data[url].services|, we still need to put the restart handler on the
// controller. When the agent crashes, |data[url].services| often gets closed
// quite a bit earlier (~1 second) than the agent runner notices via the
// application controller (which it must use as opposed to any interface on
// the agent itself since the agent is not required to implement any
// interfaces itself, even though it is recommended that it does). If we try
// to restart the agent at that time, the agent runner would attempt to simply
// send the connection request to the crashed agent instance and not relaunch
// the agent.
//
// It is also because of this delay that we must queue any pending service
// connection requests until we can restart.
agent_data->controller.set_error_handler([this, url](zx_status_t status) {
auto it = session_agents_.find(url);
FXL_DCHECK(it != session_agents_.end())
<< "Controller and services not registered for " << url;
auto& agent_data = it->second;
agent_data.services.Unbind();
agent_data.controller.Unbind();
ReportSessionAgentEvent(url, SessionAgentEvent::CRASH);
if (agent_data.restart.ShouldRetry()) {
FXL_LOG(INFO) << "Restarting " << url << "...";
StartSessionAgent(url);
} else {
FXL_LOG(WARNING) << url << " failed to restart more than "
<< kSessionAgentRetryLimit.count << " times in "
<< kSessionAgentRetryLimit.period.to_secs()
<< " seconds.";
ReportSessionAgentEvent(url, SessionAgentEvent::CRASH_LIMIT);
// Erase so that incoming connection requests fail fast rather than
// enqueue forever.
session_agents_.erase(it);
}
});
}
std::vector<std::string> UserIntelligenceProviderImpl::AddAgentServices(
const std::string& url, component::ServiceNamespace* agent_host) {
fuchsia::modular::ComponentScope agent_info;
fuchsia::modular::AgentScope agent_scope;
agent_scope.url = url;
agent_info.set_agent_scope(std::move(agent_scope));
std::vector<std::string> service_names;
service_names.push_back(fuchsia::modular::ContextWriter::Name_);
agent_host->AddService<fuchsia::modular::ContextWriter>(fxl::MakeCopyable(
[this, client_info = CloneScope(agent_info),
url](fidl::InterfaceRequest<fuchsia::modular::ContextWriter> request) {
context_engine_->GetWriter(CloneScope(client_info), std::move(request));
}));
service_names.push_back(fuchsia::modular::ContextReader::Name_);
agent_host->AddService<fuchsia::modular::ContextReader>(fxl::MakeCopyable(
[this, client_info = CloneScope(agent_info),
url](fidl::InterfaceRequest<fuchsia::modular::ContextReader> request) {
context_engine_->GetReader(CloneScope(client_info), std::move(request));
}));
service_names.push_back(fuchsia::modular::IntelligenceServices::Name_);
agent_host->AddService<fuchsia::modular::IntelligenceServices>(
fxl::MakeCopyable(
[this, client_info = CloneScope(agent_info),
url](fidl::InterfaceRequest<fuchsia::modular::IntelligenceServices>
request) {
this->GetComponentIntelligenceServices(CloneScope(client_info),
std::move(request));
}));
service_names.push_back(fuchsia::modular::ProposalPublisher::Name_);
agent_host->AddService<fuchsia::modular::ProposalPublisher>(
[this, url](
fidl::InterfaceRequest<fuchsia::modular::ProposalPublisher> request) {
suggestion_engine_->RegisterProposalPublisher(url, std::move(request));
});
if (url == kUsageLogUrl) {
service_names.push_back(fuchsia::modular::ContextDebug::Name_);
agent_host->AddService<fuchsia::modular::ContextDebug>(
[this](fidl::InterfaceRequest<fuchsia::modular::ContextDebug> request) {
context_engine_->GetContextDebug(std::move(request));
});
service_names.push_back(fuchsia::modular::SuggestionDebug::Name_);
agent_host->AddService<fuchsia::modular::SuggestionDebug>(
[this](
fidl::InterfaceRequest<fuchsia::modular::SuggestionDebug> request) {
suggestion_services_.ConnectToService(std::move(request));
});
}
if (url == kStoryInfoAgentUrl) {
service_names.push_back(fuchsia::modular::VisibleStoriesProvider::Name_);
agent_host->AddService<fuchsia::modular::VisibleStoriesProvider>(
[this](fidl::InterfaceRequest<fuchsia::modular::VisibleStoriesProvider>
request) {
visible_stories_provider_connector_(std::move(request));
});
service_names.push_back(fuchsia::modular::StoryProvider::Name_);
agent_host->AddService<fuchsia::modular::StoryProvider>(
[this](
fidl::InterfaceRequest<fuchsia::modular::StoryProvider> request) {
story_provider_connector_(std::move(request));
});
service_names.push_back(fuchsia::modular::FocusProvider::Name_);
agent_host->AddService<fuchsia::modular::FocusProvider>(
[this](
fidl::InterfaceRequest<fuchsia::modular::FocusProvider> request) {
focus_provider_connector_(std::move(request));
});
}
if (session_agents_.find(url) != session_agents_.end()) {
// All services added below should be exclusive to session agents.
service_names.push_back(fuchsia::modular::PuppetMaster::Name_);
agent_host->AddService<fuchsia::modular::PuppetMaster>(
[this](fidl::InterfaceRequest<fuchsia::modular::PuppetMaster> request) {
puppet_master_connector_(std::move(request));
});
service_names.push_back(fuchsia::modular::FocusProvider::Name_);
agent_host->AddService<fuchsia::modular::FocusProvider>(
[this,
url](fidl::InterfaceRequest<fuchsia::modular::FocusProvider> request) {
focus_provider_connector_(std::move(request));
});
}
return service_names;
}
} // namespace modular