blob: 2157a6c309e1f41cbb7485be74684873febff771 [file] [log] [blame]
// Copyright 2016 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 "src/modular/bin/sessionmgr/story_runner/story_controller_impl.h"
#include <fuchsia/intl/cpp/fidl.h>
#include <fuchsia/modular/cpp/fidl.h>
#include <fuchsia/modular/internal/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <lib/async/default.h>
#include <lib/fidl/cpp/clone.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/fidl/cpp/interface_ptr_set.h>
#include <lib/fidl/cpp/interface_request.h>
#include <lib/fidl/cpp/optional.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <lib/zx/eventpair.h>
#include <zircon/time.h>
#include <ctime>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "src/lib/fsl/types/type_converters.h"
#include "src/lib/fsl/vmo/strings.h"
#include "src/lib/fxl/strings/join_strings.h"
#include "src/lib/fxl/strings/split_string.h"
#include "src/modular/bin/sessionmgr/annotations.h"
#include "src/modular/bin/sessionmgr/puppet_master/command_runners/operation_calls/add_mod_call.h"
#include "src/modular/bin/sessionmgr/storage/story_storage.h"
#include "src/modular/bin/sessionmgr/story_runner/module_context_impl.h"
#include "src/modular/bin/sessionmgr/story_runner/module_controller_impl.h"
#include "src/modular/bin/sessionmgr/story_runner/ongoing_activity_impl.h"
#include "src/modular/bin/sessionmgr/story_runner/story_provider_impl.h"
#include "src/modular/bin/sessionmgr/story_runner/story_shell_context_impl.h"
#include "src/modular/lib/async/cpp/future.h"
#include "src/modular/lib/common/teardown.h"
#include "src/modular/lib/fidl/array_to_string.h"
#include "src/modular/lib/fidl/clone.h"
#include "src/modular/lib/string_escape/string_escape.h"
namespace modular {
namespace {
constexpr char kSurfaceIDSeparator[] = ":";
std::string ModulePathToSurfaceID(const std::vector<std::string>& module_path) {
std::vector<std::string> path;
// Sanitize all the |module_name|s that make up this |module_path|.
for (const auto& module_name : module_path) {
path.push_back(StringEscape(module_name, kSurfaceIDSeparator));
}
return fxl::JoinStrings(path, kSurfaceIDSeparator);
}
std::vector<std::string> ModulePathFromSurfaceID(const std::string& surface_id) {
std::vector<std::string> path;
for (const auto& parts :
SplitEscapedString(std::string_view(surface_id), kSurfaceIDSeparator[0])) {
path.emplace_back(parts);
}
return path;
}
std::vector<std::string> ParentModulePath(const std::vector<std::string>& module_path) {
std::vector<std::string> ret;
if (module_path.size() > 0) {
for (size_t i = 0; i < module_path.size() - 1; i++) {
ret.push_back(module_path.at(i));
}
}
return ret;
}
} // namespace
bool ShouldRestartModuleForNewIntent(const fuchsia::modular::Intent& old_intent,
const fuchsia::modular::Intent& new_intent) {
if (old_intent.handler != new_intent.handler) {
return true;
}
return false;
}
zx_time_t GetNowUTC() {
std::timespec ts;
FX_CHECK(std::timespec_get(&ts, TIME_UTC) != 0);
return zx_time_from_timespec(ts);
}
void StoryControllerImpl::RunningModInfo::InitializeInspectProperties(
StoryControllerImpl* const story_controller_impl) {
mod_inspect_node =
story_controller_impl->story_inspect_node_->CreateChild(module_data->module_url());
std::string is_embedded_str;
if (module_data->is_embedded()) {
is_embedded_str = "True";
} else {
is_embedded_str = "False";
}
is_embedded_property =
mod_inspect_node.CreateString(modular_config::kInspectIsEmbedded, is_embedded_str);
std::string is_deleted_str;
if (module_data->module_deleted()) {
is_deleted_str = "True";
} else {
is_deleted_str = "False";
}
is_deleted_property =
mod_inspect_node.CreateString(modular_config::kInspectIsDeleted, is_deleted_str);
std::string mod_source_string;
switch (module_data->module_source()) {
case fuchsia::modular::ModuleSource::INTERNAL:
mod_source_string = "INTERNAL";
break;
case fuchsia::modular::ModuleSource::EXTERNAL:
mod_source_string = "EXTERNAL";
break;
}
module_source_property =
mod_inspect_node.CreateString(modular_config::kInspectModuleSource, mod_source_string);
module_intent_action_property = mod_inspect_node.CreateString(
modular_config::kInspectIntentAction, module_data->intent().action.value_or(""));
std::string module_path_str = fxl::JoinStrings(module_data->module_path(), ", ");
module_path_property =
mod_inspect_node.CreateString(modular_config::kInspectModulePath, module_path_str);
if (module_data->has_surface_relation()) {
std::string arrangement;
switch (module_data->surface_relation().arrangement) {
case fuchsia::modular::SurfaceArrangement::COPRESENT:
arrangement = "COPRESENT";
break;
case fuchsia::modular::SurfaceArrangement::SEQUENTIAL:
arrangement = "SEQUENTIAL";
break;
case fuchsia::modular::SurfaceArrangement::ONTOP:
arrangement = "ONTOP";
break;
case fuchsia::modular::SurfaceArrangement::NONE:
arrangement = "NONE";
break;
}
module_surface_relation_arrangement = mod_inspect_node.CreateString(
modular_config::kInspectSurfaceRelationArrangement, arrangement);
std::string dependency;
switch (module_data->surface_relation().dependency) {
case fuchsia::modular::SurfaceDependency::DEPENDENT:
dependency = "DEPENDENT";
break;
case fuchsia::modular::SurfaceDependency::NONE:
dependency = "NONE";
break;
}
module_surface_relation_dependency = mod_inspect_node.CreateString(
modular_config::kInspectSurfaceRelationDependency, dependency);
module_surface_relation_emphasis = mod_inspect_node.CreateDouble(
modular_config::kInspectSurfaceRelationEmphasis, module_data->surface_relation().emphasis);
}
UpdateInspectProperties();
}
void StoryControllerImpl::RunningModInfo::UpdateInspectProperties() {
module_intent_action_property.Set(module_data->intent().action.value_or(""));
std::string param_names_str = "";
if (module_data->intent().parameters.has_value()) {
for (auto& param : *module_data->intent().parameters) {
param_names_str.append("name : " + param.name.value_or("") + " ");
}
}
module_intent_params_property.Set(param_names_str);
if (module_data->has_annotations()) {
for (const fuchsia::modular::Annotation& annotation : module_data->annotations()) {
std::string value_str = modular::annotations::ToInspect(*annotation.value.get());
std::string key_with_prefix = "annotation: " + annotation.key;
if (annotation_properties.find(key_with_prefix) != annotation_properties.end()) {
annotation_properties[key_with_prefix].Set(value_str);
} else {
annotation_properties.insert(std::pair<const std::string, inspect::StringProperty>(
key_with_prefix, mod_inspect_node.CreateString(key_with_prefix, value_str)));
}
}
}
}
// TeardownModuleCall tears down the module runtime identified by |module_path|, optionally
// notifying the story shell of the fact that the module should be defocused.
class StoryControllerImpl::TeardownModuleCall : public Operation<> {
public:
TeardownModuleCall(StoryControllerImpl* const story_controller_impl,
std::vector<std::string> module_path, bool notify_story_shell, ResultCall done)
: Operation("StoryControllerImpl::TeardownModuleCall", std::move(done)),
story_controller_impl_(story_controller_impl),
module_path_(std::move(module_path)),
notify_story_shell_(std::move(notify_story_shell)) {}
private:
void Run() override {
FlowToken flow{this};
auto* const running_mod_info = story_controller_impl_->FindRunningModInfo(module_path_);
FX_CHECK(running_mod_info != nullptr) << ModulePathToSurfaceID(module_path_);
// If the module is external, we also notify story shell about it going
// away. An internal module is stopped by its parent module, and it's up to
// the parent module to defocus it first. TODO(mesch): Why not always
// defocus?
auto future = Future<>::Create("StoryControllerImpl.TeardownModuleCall.Run.future");
if (notify_story_shell_ && story_controller_impl_->story_shell_ &&
running_mod_info->module_data->module_source() ==
fuchsia::modular::ModuleSource::EXTERNAL) {
story_controller_impl_->story_shell_->DefocusSurface(ModulePathToSurfaceID(module_path_),
future->Completer());
} else {
future->Complete();
}
future->Then([this, flow] {
auto* const running_mod_info = story_controller_impl_->FindRunningModInfo(module_path_);
FX_CHECK(running_mod_info != nullptr) << ModulePathToSurfaceID(module_path_);
running_mod_info->module_controller_impl->Teardown(
[flow, this] { story_controller_impl_->EraseRunningModInfo(module_path_); });
});
}
StoryControllerImpl* const story_controller_impl_; // not owned
const std::vector<std::string> module_path_;
const bool notify_story_shell_;
};
// Launches (brings up a running instance) of a module.
//
// If the module is to be composed into the story shell, notifies the story
// shell of the new module. If the module is composed internally, connects the
// view owner request appropriately.
class StoryControllerImpl::LaunchModuleCall : public Operation<> {
public:
LaunchModuleCall(StoryControllerImpl* const story_controller_impl,
fuchsia::modular::ModuleData module_data, ResultCall result_call)
: Operation("StoryControllerImpl::LaunchModuleCall", std::move(result_call)),
story_controller_impl_(story_controller_impl),
module_data_(std::move(module_data)) {}
private:
void Run() override {
FlowToken flow{this};
RunningModInfo* const running_mod_info =
story_controller_impl_->FindRunningModInfo(module_data_.module_path());
// We launch the new module if it doesn't run yet.
if (!running_mod_info) {
Launch(flow);
return;
}
// If the new module is already running, but with a different Intent, we
// tear it down then launch a new instance.
if (ShouldRestartModuleForNewIntent(running_mod_info->module_data->intent(),
module_data_.intent())) {
operation_queue_.Add(std::make_unique<TeardownModuleCall>(
story_controller_impl_, module_data_.module_path(), /*notify_story_shell=*/false,
[this, flow] { Launch(flow); }));
}
}
void Launch(FlowToken /*flow*/) {
FX_LOGS(INFO) << "StoryControllerImpl::LaunchModule() " << module_data_.module_url() << " "
<< ModulePathToSurfaceID(module_data_.module_path());
fuchsia::modular::session::AppConfig module_config;
module_config.set_url(module_data_.module_url());
fuchsia::sys::ServiceProviderPtr module_context_provider;
auto module_context_provider_request = module_context_provider.NewRequest();
auto service_list = fuchsia::sys::ServiceList::New();
for (const auto& service_name :
story_controller_impl_->story_provider_impl_->component_context_info()
.agent_runner->GetAgentServices()) {
service_list->names.push_back(service_name);
}
service_list->names.push_back(fuchsia::modular::ComponentContext::Name_);
service_list->names.push_back(fuchsia::modular::ModuleContext::Name_);
service_list->names.push_back(fuchsia::intl::PropertyProvider::Name_);
if (module_data_.has_additional_services()) {
for (const auto& service_name : module_data_.additional_services().names) {
service_list->names.push_back(service_name);
}
}
service_list->provider = std::move(module_context_provider);
auto running_mod_info = std::make_unique<RunningModInfo>();
running_mod_info->module_data = fidl::MakeOptional(CloneModuleData(module_data_));
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
scenic::ViewRefPair view_ref_pair = scenic::ViewRefPair::New();
auto view_ref_clone = fidl::Clone(view_ref_pair.view_ref);
// ModuleControllerImpl's constructor launches the child application.
running_mod_info->module_controller_impl = std::make_unique<ModuleControllerImpl>(
story_controller_impl_->story_provider_impl_->session_environment()->GetLauncher(),
std::move(module_config), running_mod_info->module_data.get(), std::move(service_list),
std::move(view_token), std::move(view_ref_pair));
running_mod_info->pending_view_holder_token = std::move(view_holder_token);
running_mod_info->view_ref = std::move(view_ref_clone);
ModuleContextInfo module_context_info = {
story_controller_impl_->story_provider_impl_->component_context_info(),
story_controller_impl_,
story_controller_impl_->story_provider_impl_->session_environment(),
};
running_mod_info->module_context_impl =
std::make_unique<ModuleContextImpl>(module_context_info, CloneModuleData(module_data_),
std::move(module_context_provider_request));
running_mod_info->InitializeInspectProperties(story_controller_impl_);
story_controller_impl_->running_mod_infos_.emplace_back(std::move(running_mod_info));
for (auto& i : story_controller_impl_->watchers_.ptrs()) {
(*i)->OnModuleAdded(CloneModuleData(module_data_));
}
}
OperationQueue operation_queue_;
StoryControllerImpl* const story_controller_impl_; // not owned
fuchsia::modular::ModuleData module_data_;
};
// Calls LaunchModuleCall to get a running instance, and delegates visual
// composition to the story shell.
class StoryControllerImpl::LaunchModuleInShellCall : public Operation<> {
public:
LaunchModuleInShellCall(StoryControllerImpl* const story_controller_impl,
fuchsia::modular::ModuleData module_data, ResultCall result_call)
: Operation("StoryControllerImpl::LaunchModuleInShellCall", std::move(result_call),
module_data.module_url()),
story_controller_impl_(story_controller_impl),
module_data_(std::move(module_data)) {}
private:
void Run() override {
FlowToken flow{this};
operation_queue_.Add(
std::make_unique<LaunchModuleCall>(story_controller_impl_, CloneModuleData(module_data_),
[this, flow] { MaybeConnectViewToStoryShell(flow); }));
}
// We only add a module to story shell if it's either a root module or its
// anchor module is already known to story shell. Otherwise, we pend its view
// (StoryControllerImpl::pending_story_shell_views_) and pass it to the story
// shell once its anchor module is ready.
void MaybeConnectViewToStoryShell(FlowToken flow) {
// If this is called during Stop(), story_shell_ might already have been
// reset. TODO(mesch): Then the whole operation should fail.
if (!story_controller_impl_->story_shell_) {
return;
}
auto* const running_mod_info =
story_controller_impl_->FindRunningModInfo(module_data_.module_path());
FX_CHECK(running_mod_info); // This was just created in LaunchModuleCall.
std::string anchor_surface_id;
auto* const anchor = story_controller_impl_->FindAnchor(running_mod_info);
if (anchor) {
anchor_surface_id = ModulePathToSurfaceID(anchor->module_data->module_path());
}
const auto surface_id = ModulePathToSurfaceID(module_data_.module_path());
fuchsia::modular::ViewConnection view_connection;
view_connection.surface_id = surface_id;
view_connection.view_holder_token = std::move(*running_mod_info->pending_view_holder_token);
fuchsia::modular::SurfaceInfo2 surface_info;
surface_info.set_parent_id(anchor_surface_id);
if (module_data_.has_surface_relation()) {
surface_info.set_surface_relation(fidl::Clone(module_data_.surface_relation()));
}
surface_info.set_module_source(module_data_.module_source());
surface_info.set_view_ref(fidl::Clone(*running_mod_info->view_ref));
// If this is a root module, or the anchor module is connected to the story shell,
// connect this module to the story shell. Otherwise, pend it to connect once the anchor
// module is ready.
if (module_data_.module_path().size() == 1 ||
story_controller_impl_->connected_views_.count(anchor_surface_id)) {
ConnectViewToStoryShell(flow, std::move(view_connection), std::move(surface_info));
} else {
story_controller_impl_->pending_story_shell_views_.emplace(
ModulePathToSurfaceID(module_data_.module_path()),
PendingViewForStoryShell{module_data_.module_path(), std::move(view_connection),
std::move(surface_info)});
}
}
void ConnectViewToStoryShell(FlowToken flow, fuchsia::modular::ViewConnection view_connection,
fuchsia::modular::SurfaceInfo2 surface_info) {
if (!view_connection.view_holder_token.value) {
FX_LOGS(WARNING) << "The module ViewHolder token is not valid, so it "
"can't be sent to the story shell.";
return;
}
const auto surface_id = ModulePathToSurfaceID(module_data_.module_path());
story_controller_impl_->story_shell_->AddSurface3(std::move(view_connection),
std::move(surface_info));
story_controller_impl_->connected_views_.emplace(surface_id);
story_controller_impl_->ProcessPendingStoryShellViews();
story_controller_impl_->story_shell_->FocusSurface(surface_id);
}
StoryControllerImpl* const story_controller_impl_; // not owned
fuchsia::modular::ModuleData module_data_;
OperationQueue operation_queue_;
};
class StoryControllerImpl::TeardownStoryCall : public Operation<> {
public:
TeardownStoryCall(StoryControllerImpl* const story_controller_impl,
const bool skip_notifying_sessionshell, fit::function<void()> done)
: Operation("StoryControllerImpl::TeardownStoryCall", std::move(done)),
story_controller_impl_(story_controller_impl),
skip_notifying_sessionshell_(skip_notifying_sessionshell) {}
private:
void Run() override {
if (!story_controller_impl_->IsRunning()) {
Done();
return;
}
story_controller_impl_->SetRuntimeState(fuchsia::modular::StoryState::STOPPING);
if (skip_notifying_sessionshell_) {
StopStory();
return;
}
// Invocation of DetachView() follows below.
//
// The following callback is scheduled twice, once as response from
// DetachView(), and again as a timeout.
//
// The weak pointer is needed because the method invocation would not be
// cancelled when the OperationQueue holding this Operation instance is
// deleted, because the method is invoked on an instance outside of the
// instance that owns the OperationQueue that holds this Operation instance.
//
// The argument from_timeout informs whether the invocation was from the
// timeout or from the method callback. It's used only to log diagnostics.
fit::callback<void(const bool)> cont =
[this, weak_this = GetWeakPtr(),
story_id = story_controller_impl_->story_id_](const bool from_timeout) {
if (from_timeout) {
FX_LOGS(INFO) << "DetachView() timed out: story_id=" << story_id;
}
if (weak_this) {
StopStory();
}
};
// We need to attach the callback to both DetachView() and to the timeout
// (PostDelayedTask(), below). |fit::function| is move-only, not copyable,
// but we can use the share() method to get a reference-counted copy.
// Note the fit::function will not be destructed until all callers have
// released their reference, so don't pass a |FlowToken| to the callback,
// or it might keep the |Operation| alive longer than you might expect.
story_controller_impl_->DetachView([cont = cont.share()]() mutable {
if (cont)
cont(false);
});
async::PostDelayedTask(
async_get_default_dispatcher(),
[cont = std::move(cont)]() mutable {
if (cont)
cont(true);
},
kBasicTimeout);
}
void StopStory() {
std::vector<FuturePtr<>> did_teardowns;
did_teardowns.reserve(story_controller_impl_->running_mod_infos_.size());
// Tear down all modules.
for (auto& running_mod_info : story_controller_impl_->running_mod_infos_) {
auto did_teardown =
Future<>::Create("StoryControllerImpl.TeardownStoryCall.Run.did_teardown");
operation_collection_.Add(std::make_unique<TeardownModuleCall>(
story_controller_impl_, running_mod_info->module_data->module_path(),
/*notify_story_shell=*/false, did_teardown->Completer()));
did_teardowns.emplace_back(did_teardown);
}
Wait("StoryControllerImpl.TeardownStoryCall.Run.Wait", did_teardowns)
->AsyncMap([this] {
auto did_teardown =
Future<>::Create("StoryControllerImpl.TeardownStoryCall.Run.did_teardown2");
// If TeardownStoryCall runs on a story that's not running, there is no story
// shell.
if (story_controller_impl_->story_shell_holder_) {
story_controller_impl_->story_shell_holder_->Teardown(kBasicTimeout,
did_teardown->Completer());
} else {
did_teardown->Complete();
}
return did_teardown;
})
->Then([this] {
story_controller_impl_->story_shell_holder_.reset();
story_controller_impl_->story_shell_.Unbind();
story_controller_impl_->SetRuntimeState(fuchsia::modular::StoryState::STOPPED);
Done();
});
}
OperationCollection operation_collection_;
StoryControllerImpl* const story_controller_impl_; // not owned
// Whether this Stop operation is part of stopping all stories at once. In
// that case, DetachView() is not called.
const bool skip_notifying_sessionshell_;
};
class StoryControllerImpl::DeleteModuleCall : public Operation<> {
public:
DeleteModuleCall(StoryStorage* const story_storage, std::vector<std::string> module_path,
fit::function<void()> done)
: Operation("StoryControllerImpl::DeleteModuleCall", std::move(done)),
story_storage_(story_storage),
module_path_(std::move(module_path)) {}
private:
void Run() override {
FlowToken flow{this};
// Mark the module as deleted. The module's runtime will be torn down
// once the StoryStorage notifies us of the change to ModuleData.
FX_CHECK(story_storage_->MarkModuleAsDeleted(module_path_));
}
StoryStorage* const story_storage_; // not owned
const std::vector<std::string> module_path_;
};
class StoryControllerImpl::DeleteModuleAndTeardownStoryIfEmptyCall : public Operation<> {
public:
DeleteModuleAndTeardownStoryIfEmptyCall(StoryControllerImpl* const story_controller_impl,
std::vector<std::string> module_path,
fit::function<void()> done)
: Operation("StoryControllerImpl::DeleteModuleAndTeardownStoryIfEmptyCall", std::move(done)),
story_controller_impl_(story_controller_impl),
module_path_(std::move(module_path)) {}
private:
void Run() override {
FlowToken flow{this};
operation_queue_.Add(std::make_unique<DeleteModuleCall>(story_controller_impl_->story_storage_,
module_path_, [flow] {}));
// If this is the last module in the story, tear down the story as well.
auto* const running_mod_info = story_controller_impl_->FindRunningModInfo(module_path_);
if (running_mod_info && story_controller_impl_->running_mod_infos_.size() == 1) {
operation_queue_.Add(std::make_unique<TeardownStoryCall>(
story_controller_impl_, false /* skip_notifying_sessionshell */, [flow] {}));
}
}
StoryControllerImpl* const story_controller_impl_; // not owned
const std::vector<std::string> module_path_;
OperationQueue operation_queue_;
};
class StoryControllerImpl::OnModuleDataUpdatedCall : public Operation<> {
public:
OnModuleDataUpdatedCall(StoryControllerImpl* const story_controller_impl,
fuchsia::modular::ModuleData module_data)
: Operation("StoryControllerImpl::OnModuleDataUpdatedCall", [] {}),
story_controller_impl_(story_controller_impl),
module_data_(std::move(module_data)) {}
private:
void Run() override {
FlowToken flow{this};
if (!story_controller_impl_->IsRunning()) {
return;
}
// Check for existing module at the given path.
auto* const running_mod_info =
story_controller_impl_->FindRunningModInfo(module_data_.module_path());
if (module_data_.module_deleted()) {
// If the module is running, kill it.
if (running_mod_info) {
running_mod_info->is_deleted_property.Set("True");
operation_queue_.Add(
std::make_unique<TeardownModuleCall>(story_controller_impl_, module_data_.module_path(),
/*notify_story_shell=*/true, [flow] {}));
}
return;
}
// We do not auto-start Modules that were added through ModuleContext on
// other devices.
//
// TODO(thatguy): Revisit this decision. It seems wrong: we do not want to
// auto-start mods added through ModuleContext.EmbedModule(), because we do
// not have the necessary capabilities (the ViewHolderToken). Mods added
// through ModuleContext.AddModuleToStory() can be started automatically,
// however.
if (module_data_.module_source() == fuchsia::modular::ModuleSource::INTERNAL) {
return;
}
// We reach this point only if we want to start or update an existing
// external module.
operation_queue_.Add(std::make_unique<LaunchModuleInShellCall>(
story_controller_impl_, std::move(module_data_), [flow] {}));
}
OperationQueue operation_queue_;
StoryControllerImpl* const story_controller_impl_; // not owned
fuchsia::modular::ModuleData module_data_;
};
class StoryControllerImpl::FocusCall : public Operation<> {
public:
FocusCall(StoryControllerImpl* const story_controller_impl, std::vector<std::string> module_path)
: Operation("StoryControllerImpl::FocusCall", [] {}),
story_controller_impl_(story_controller_impl),
module_path_(std::move(module_path)) {}
private:
void Run() override {
FlowToken flow{this};
if (!story_controller_impl_->story_shell_) {
return;
}
story_controller_impl_->story_shell_->FocusSurface(ModulePathToSurfaceID(module_path_));
}
OperationQueue operation_queue_;
StoryControllerImpl* const story_controller_impl_; // not owned
const std::vector<std::string> module_path_;
};
class StoryControllerImpl::DefocusCall : public Operation<> {
public:
DefocusCall(StoryControllerImpl* const story_controller_impl,
std::vector<std::string> module_path)
: Operation("StoryControllerImpl::DefocusCall", [] {}),
story_controller_impl_(story_controller_impl),
module_path_(std::move(module_path)) {}
private:
void Run() override {
FlowToken flow{this};
if (!story_controller_impl_->story_shell_) {
return;
}
// NOTE(mesch): We don't wait for defocus to return. TODO(mesch): What is
// the return callback good for anyway?
story_controller_impl_->story_shell_->DefocusSurface(ModulePathToSurfaceID(module_path_),
[] {});
}
OperationQueue operation_queue_;
StoryControllerImpl* const story_controller_impl_; // not owned
const std::vector<std::string> module_path_;
};
class StoryControllerImpl::StartCall : public Operation<> {
public:
StartCall(StoryControllerImpl* const story_controller_impl, StoryStorage* const storage)
: Operation("StoryControllerImpl::StartCall", [] {}),
story_controller_impl_(story_controller_impl),
storage_(storage) {}
private:
void Run() override {
FlowToken flow{this};
// If the story is running, we do nothing.
if (story_controller_impl_->IsRunning()) {
FX_LOGS(INFO) << "StoryControllerImpl::StartCall() while already running: ignored.";
return;
}
story_controller_impl_->StartStoryShell();
// Start all modules that were not themselves explicitly started by another
// module.
auto all_data = storage_->ReadAllModuleData();
for (auto& module_data : all_data) {
// Don't start the module if it is embedded, or if it has been
// marked deleted.
if (module_data.module_deleted() || module_data.is_embedded()) {
continue;
}
FX_CHECK(module_data.has_intent());
operation_queue_.Add(std::make_unique<LaunchModuleInShellCall>(
story_controller_impl_, std::move(module_data), [flow] {}));
}
story_controller_impl_->SetRuntimeState(fuchsia::modular::StoryState::RUNNING);
}
StoryControllerImpl* const story_controller_impl_; // not owned
StoryStorage* const storage_; // not owned
OperationQueue operation_queue_;
};
StoryControllerImpl::StoryControllerImpl(std::string story_id,
SessionStorage* const session_storage,
StoryStorage* const story_storage,
StoryProviderImpl* story_provider_impl,
inspect::Node* story_inspect_node)
: story_id_(std::move(story_id)),
story_provider_impl_(story_provider_impl),
session_storage_(session_storage),
story_storage_(story_storage),
story_inspect_node_(story_inspect_node),
story_shell_context_impl_{story_id_},
weak_factory_(this) {
story_storage_->SubscribeModuleDataUpdated(
[this](const fuchsia::modular::ModuleData& module_data) {
auto* const running_mod_info = FindRunningModInfo(module_data.module_path());
if (running_mod_info) {
if (module_data.has_annotations()) {
fidl::Clone(module_data.annotations(),
running_mod_info->module_data->mutable_annotations());
}
running_mod_info->UpdateInspectProperties();
}
OnModuleDataUpdated(CloneModuleData(module_data));
return WatchInterest::kContinue;
});
}
StoryControllerImpl::~StoryControllerImpl() = default;
void StoryControllerImpl::Connect(
fidl::InterfaceRequest<fuchsia::modular::StoryController> request) {
bindings_.AddBinding(this, std::move(request));
}
bool StoryControllerImpl::IsRunning() {
switch (runtime_state_) {
case fuchsia::modular::StoryState::RUNNING:
return true;
case fuchsia::modular::StoryState::STOPPING:
case fuchsia::modular::StoryState::STOPPED:
return false;
}
}
void StoryControllerImpl::Sync(fit::function<void()> done) {
operation_queue_.Add(std::make_unique<SyncCall>(std::move(done)));
}
void StoryControllerImpl::FocusModule(const std::vector<std::string>& module_path) {
operation_queue_.Add(std::make_unique<FocusCall>(this, module_path));
}
void StoryControllerImpl::DefocusModule(const std::vector<std::string>& module_path) {
operation_queue_.Add(std::make_unique<DefocusCall>(this, module_path));
}
void StoryControllerImpl::DeleteModule(const std::vector<std::string>& module_path,
fit::function<void()> done) {
operation_queue_.Add(
std::make_unique<DeleteModuleCall>(story_storage_, module_path, std::move(done)));
}
void StoryControllerImpl::ProcessPendingStoryShellViews() {
// NOTE(mesch): As it stands, this machinery to send modules in traversal
// order to the story shell is N^3 over the lifetime of the story, where N
// is the number of modules. This function is N^2, and it's called once for
// each of the N modules. However, N is small, and moreover its scale is
// limited my much more severe constraints. Eventually, we will address this
// by changing story shell to be able to accomodate modules out of traversal
// order.
if (!story_shell_) {
return;
}
std::vector<fidl::StringPtr> added_keys;
for (auto& kv : pending_story_shell_views_) {
auto* const running_mod_info = FindRunningModInfo(kv.second.module_path);
if (!running_mod_info) {
continue;
}
auto* const anchor = FindAnchor(running_mod_info);
if (!anchor) {
continue;
}
const auto anchor_surface_id = ModulePathToSurfaceID(anchor->module_data->module_path());
if (!connected_views_.count(anchor_surface_id)) {
continue;
}
if (!kv.second.view_connection.view_holder_token.value) {
FX_LOGS(WARNING) << "The module ViewHolder token is not valid, so it "
"can't be sent to the story shell.";
// Erase the pending view in this case, as it will never become valid.
added_keys.push_back(kv.first);
continue;
}
const auto surface_id = ModulePathToSurfaceID(kv.second.module_path);
story_shell_->AddSurface3(std::move(kv.second.view_connection),
std::move(kv.second.surface_info));
connected_views_.emplace(surface_id);
added_keys.push_back(kv.first);
}
if (added_keys.size()) {
for (auto& key : added_keys) {
pending_story_shell_views_.erase(key.value_or(""));
}
ProcessPendingStoryShellViews();
}
}
void StoryControllerImpl::OnModuleDataUpdated(fuchsia::modular::ModuleData module_data) {
operation_queue_.Add(std::make_unique<OnModuleDataUpdatedCall>(this, std::move(module_data)));
}
void StoryControllerImpl::GetInfo(GetInfoCallback callback) {
// Synced such that if GetInfo() is called after Start() or Stop(), the
// state after the previously invoked operation is returned.
//
// If this call enters a race with a StoryProvider.DeleteStory() call,
// resulting in |this| being destroyed, |callback| will be dropped.
operation_queue_.Add(std::make_unique<SyncCall>([this, callback = std::move(callback)] {
auto story_info_2 = story_provider_impl_->GetCachedStoryInfo(story_id_);
FX_CHECK(story_info_2);
auto story_info = modular::StoryProviderImpl::StoryInfo2ToStoryInfo(*story_info_2);
callback(std::move(story_info), runtime_state_);
}));
}
void StoryControllerImpl::GetInfo2(GetInfo2Callback callback) {
// Synced such that if GetInfo() is called after Start() or Stop(), the
// state after the previously invoked operation is returned.
//
// If this call enters a race with a StoryProvider.DeleteStory() call,
// resulting in |this| being destroyed, |callback| will be dropped.
operation_queue_.Add(std::make_unique<SyncCall>([this, callback = std::move(callback)] {
auto story_info_2 = story_provider_impl_->GetCachedStoryInfo(story_id_);
FX_CHECK(story_info_2);
callback(std::move(*story_info_2), runtime_state_);
}));
}
void StoryControllerImpl::RequestStart() {
operation_queue_.Add(std::make_unique<StartCall>(this, story_storage_));
}
void StoryControllerImpl::Stop(StopCallback done) {
operation_queue_.Add(std::make_unique<TeardownStoryCall>(
this, false /* skip_notifying_sessionshell */, std::move(done)));
}
void StoryControllerImpl::Teardown(const bool skip_notifying_sessionshell, StopCallback done) {
operation_queue_.Add(
std::make_unique<TeardownStoryCall>(this, skip_notifying_sessionshell, std::move(done)));
}
void StoryControllerImpl::Watch(fidl::InterfaceHandle<fuchsia::modular::StoryWatcher> watcher) {
auto ptr = watcher.Bind();
NotifyOneStoryWatcher(ptr.get());
watchers_.AddInterfacePtr(std::move(ptr));
}
void StoryControllerImpl::StartStoryShell() {
auto [view_token, view_holder_token] = scenic::NewViewTokenPair();
story_shell_holder_ = story_provider_impl_->StartStoryShell(story_id_, std::move(view_token),
story_shell_.NewRequest());
story_provider_impl_->AttachOrPresentView(story_id_, std::move(view_holder_token));
fuchsia::modular::StoryShellContextPtr story_shell_context;
story_shell_context_impl_.Connect(story_shell_context.NewRequest());
story_shell_->Initialize(std::move(story_shell_context));
story_shell_.events().OnSurfaceFocused =
fit::bind_member(this, &StoryControllerImpl::OnSurfaceFocused);
}
void StoryControllerImpl::DetachView(fit::function<void()> done) {
story_provider_impl_->DetachOrDismissView(story_id_, std::move(done));
}
void StoryControllerImpl::SetRuntimeState(const fuchsia::modular::StoryState new_state) {
runtime_state_ = new_state;
NotifyStoryWatchers();
story_provider_impl_->NotifyStoryStateChange(story_id_);
}
void StoryControllerImpl::NotifyStoryWatchers() {
for (auto& i : watchers_.ptrs()) {
NotifyOneStoryWatcher((*i).get());
}
}
void StoryControllerImpl::NotifyOneStoryWatcher(fuchsia::modular::StoryWatcher* watcher) {
watcher->OnStateChange(runtime_state_);
}
void StoryControllerImpl::EraseRunningModInfo(std::vector<std::string> module_path) {
auto it =
std::find_if(running_mod_infos_.begin(), running_mod_infos_.end(),
[module_path](auto& e) { return e->module_data->module_path() == module_path; });
FX_CHECK(it != running_mod_infos_.end());
pending_story_shell_views_.erase(ModulePathToSurfaceID(module_path));
running_mod_infos_.erase(it);
}
bool StoryControllerImpl::IsExternalModule(const std::vector<std::string>& module_path) {
auto* const i = FindRunningModInfo(module_path);
if (!i) {
return false;
}
return i->module_data->module_source() == fuchsia::modular::ModuleSource::EXTERNAL;
}
StoryControllerImpl::RunningModInfo* StoryControllerImpl::FindRunningModInfo(
const std::vector<std::string>& module_path) {
for (auto& c : running_mod_infos_) {
if (c->module_data->module_path() == module_path) {
return c.get();
}
}
return nullptr;
}
StoryControllerImpl::RunningModInfo* StoryControllerImpl::FindAnchor(
RunningModInfo* running_mod_info) {
if (!running_mod_info) {
return nullptr;
}
auto* anchor = FindRunningModInfo(ParentModulePath(running_mod_info->module_data->module_path()));
// Traverse up until there is a non-embedded module.
while (anchor && anchor->module_data->is_embedded()) {
anchor = FindRunningModInfo(ParentModulePath(anchor->module_data->module_path()));
}
return anchor;
}
void StoryControllerImpl::RemoveModuleFromStory(const std::vector<std::string>& module_path) {
operation_queue_.Add(
std::make_unique<DeleteModuleAndTeardownStoryIfEmptyCall>(this, module_path, [] {}));
}
void StoryControllerImpl::OnSurfaceFocused(fidl::StringPtr surface_id) {
auto module_path = ModulePathFromSurfaceID(surface_id.value_or(""));
for (auto& watcher : watchers_.ptrs()) {
(*watcher)->OnModuleFocused(std::move(module_path));
}
}
void StoryControllerImpl::Annotate(std::vector<fuchsia::modular::Annotation> annotations,
AnnotateCallback callback) {
operation_queue_.Add(std::make_unique<SyncCall>([weak_this = weak_factory_.GetWeakPtr(),
annotations = std::move(annotations),
callback = std::move(callback)]() mutable {
if (!weak_this) {
fuchsia::modular::StoryController_Annotate_Result result{};
result.set_err(fuchsia::modular::AnnotationError::NOT_FOUND);
callback(std::move(result));
return;
}
auto error = weak_this->session_storage_->MergeStoryAnnotations(weak_this->story_id_,
std::move(annotations));
fuchsia::modular::StoryController_Annotate_Result result{};
if (error.has_value()) {
result.set_err(error.value());
} else {
result.set_response({});
}
callback(std::move(result));
}));
}
} // namespace modular