| // 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 "peridot/bin/suggestion_engine/suggestion_engine_impl.h" |
| |
| #include <string> |
| |
| #include <fuchsia/modular/cpp/fidl.h> |
| #include <lib/context/cpp/context_helper.h> |
| #include <lib/fidl/cpp/optional.h> |
| #include <lib/fsl/vmo/strings.h> |
| #include <lib/fxl/functional/make_copyable.h> |
| #include <lib/fxl/time/time_delta.h> |
| #include <lib/fxl/time/time_point.h> |
| #include "lib/fidl/cpp/clone.h" |
| |
| #include "peridot/bin/suggestion_engine/auto_select_first_query_listener.h" |
| #include "peridot/bin/suggestion_engine/decision_policies/rank_over_threshold_decision_policy.h" |
| #include "peridot/bin/suggestion_engine/filters/conjugate_ranked_passive_filter.h" |
| #include "peridot/bin/suggestion_engine/filters/ranked_active_filter.h" |
| #include "peridot/bin/suggestion_engine/filters/ranked_passive_filter.h" |
| #include "peridot/bin/suggestion_engine/rankers/linear_ranker.h" |
| #include "peridot/bin/suggestion_engine/ranking_features/annoyance_ranking_feature.h" |
| #include "peridot/bin/suggestion_engine/ranking_features/dead_story_ranking_feature.h" |
| #include "peridot/bin/suggestion_engine/ranking_features/focused_story_ranking_feature.h" |
| #include "peridot/bin/suggestion_engine/ranking_features/interrupting_ranking_feature.h" |
| #include "peridot/bin/suggestion_engine/ranking_features/kronk_ranking_feature.h" |
| #include "peridot/bin/suggestion_engine/ranking_features/mod_pair_ranking_feature.h" |
| #include "peridot/bin/suggestion_engine/ranking_features/proposal_hint_ranking_feature.h" |
| #include "peridot/bin/suggestion_engine/ranking_features/query_match_ranking_feature.h" |
| #include "peridot/bin/suggestion_engine/ranking_features/ranking_feature.h" |
| #include "peridot/lib/fidl/json_xdr.h" |
| |
| namespace modular { |
| |
| std::string StoryNameKey(const std::string& source_url, |
| const std::string& story_name) { |
| return source_url + story_name; |
| } |
| |
| namespace { |
| |
| constexpr int kQueryActionMaxResults = 1; |
| |
| } // namespace |
| |
| SuggestionEngineImpl::SuggestionEngineImpl(fuchsia::media::AudioPtr audio) |
| : debug_(std::make_shared<SuggestionDebugImpl>()), |
| next_processor_(debug_), |
| query_processor_(std::move(audio), debug_), |
| context_listener_binding_(this), |
| auto_select_first_query_listener_(this), |
| auto_select_first_query_listener_binding_( |
| &auto_select_first_query_listener_) {} |
| |
| SuggestionEngineImpl::~SuggestionEngineImpl() = default; |
| |
| fxl::WeakPtr<SuggestionDebugImpl> SuggestionEngineImpl::debug() { |
| return debug_->GetWeakPtr(); |
| } |
| |
| void SuggestionEngineImpl::AddNextProposal( |
| ProposalPublisherImpl* source, fuchsia::modular::Proposal proposal) { |
| if (proposal.wants_rich_suggestion && |
| CanComponentUseRichSuggestions(source->component_url())) { |
| AddProposalWithRichSuggestion(source, std::move(proposal)); |
| } else { |
| auto story_id = |
| StoryIdFromName(source->component_url(), proposal.story_name); |
| next_processor_.AddProposal(source->component_url(), story_id, |
| std::move(proposal)); |
| } |
| } |
| |
| void SuggestionEngineImpl::ProposeNavigation( |
| const fuchsia::modular::NavigationAction navigation) { |
| navigation_processor_.Navigate(navigation); |
| } |
| |
| void SuggestionEngineImpl::AddProposalWithRichSuggestion( |
| ProposalPublisherImpl* source, fuchsia::modular::Proposal proposal) { |
| auto activity = debug_->GetIdleWaiter()->RegisterOngoingActivity(); |
| fuchsia::modular::StoryOptions story_options; |
| story_options.kind_of_proto_story = true; |
| story_provider_->CreateStoryWithOptions( |
| std::move(story_options), |
| fxl::MakeCopyable([this, proposal = std::move(proposal), |
| source_url = source->component_url(), |
| activity](fidl::StringPtr preloaded_story_id) mutable { |
| auto story_id = StoryIdFromName(source_url, proposal.story_name); |
| ExecuteActions(std::move(proposal.on_selected), |
| nullptr /* proposal_listener */, proposal.id, |
| preloaded_story_id); |
| next_processor_.AddProposal(source_url, story_id, preloaded_story_id, |
| std::move(proposal)); |
| })); |
| } |
| |
| void SuggestionEngineImpl::RemoveNextProposal(const std::string& component_url, |
| const std::string& proposal_id) { |
| SuggestionPrototype* suggestion = |
| next_processor_.GetSuggestion(component_url, proposal_id); |
| if (suggestion && !suggestion->preloaded_story_id.empty()) { |
| story_provider_->DeleteStory(suggestion->preloaded_story_id, [] {}); |
| } |
| next_processor_.RemoveProposal(component_url, proposal_id); |
| } |
| |
| void SuggestionEngineImpl::PromoteNextProposal( |
| const std::string& component_url, const std::string& preloaded_story_id, |
| const std::string& proposal_id) { |
| auto activity = debug_->GetIdleWaiter()->RegisterOngoingActivity(); |
| story_provider_->SetKindOfProtoStoryOption(preloaded_story_id, false, |
| [activity]() {}); |
| next_processor_.RemoveProposal(component_url, proposal_id); |
| } |
| |
| void SuggestionEngineImpl::Connect( |
| fidl::InterfaceRequest<fuchsia::modular::SuggestionEngine> request) { |
| bindings_.AddBinding(this, std::move(request)); |
| } |
| |
| void SuggestionEngineImpl::Connect( |
| fidl::InterfaceRequest<fuchsia::modular::SuggestionProvider> request) { |
| suggestion_provider_bindings_.AddBinding(this, std::move(request)); |
| } |
| |
| void SuggestionEngineImpl::Connect( |
| fidl::InterfaceRequest<fuchsia::modular::SuggestionDebug> request) { |
| debug_bindings_.AddBinding(debug_.get(), std::move(request)); |
| } |
| |
| // |fuchsia::modular::SuggestionProvider| |
| void SuggestionEngineImpl::Query( |
| fidl::InterfaceHandle<fuchsia::modular::QueryListener> listener, |
| fuchsia::modular::UserInput input, int count) { |
| query_processor_.ExecuteQuery(std::move(input), count, std::move(listener)); |
| } |
| |
| // |fuchsia::modular::SuggestionProvider| |
| void SuggestionEngineImpl::SubscribeToInterruptions( |
| fidl::InterfaceHandle<fuchsia::modular::InterruptionListener> listener) { |
| next_processor_.RegisterInterruptionListener(std::move(listener)); |
| } |
| |
| // |fuchsia::modular::SuggestionProvider| |
| void SuggestionEngineImpl::SubscribeToNavigation( |
| fidl::InterfaceHandle<fuchsia::modular::NavigationListener> listener) { |
| navigation_processor_.RegisterListener(std::move(listener)); |
| } |
| |
| // |fuchsia::modular::SuggestionProvider| |
| void SuggestionEngineImpl::SubscribeToNext( |
| fidl::InterfaceHandle<fuchsia::modular::NextListener> listener, int count) { |
| next_processor_.RegisterListener(std::move(listener), count); |
| } |
| |
| // |fuchsia::modular::SuggestionProvider| |
| void SuggestionEngineImpl::RegisterFeedbackListener( |
| fidl::InterfaceHandle<fuchsia::modular::FeedbackListener> speech_listener) { |
| query_processor_.RegisterFeedbackListener(std::move(speech_listener)); |
| } |
| |
| // |fuchsia::modular::SuggestionProvider| |
| void SuggestionEngineImpl::NotifyInteraction( |
| fidl::StringPtr suggestion_uuid, |
| fuchsia::modular::Interaction interaction) { |
| // Find the suggestion |
| bool suggestion_in_ask = false; |
| RankedSuggestion* suggestion = next_processor_.GetSuggestion(suggestion_uuid); |
| if (!suggestion) { |
| suggestion = query_processor_.GetSuggestion(suggestion_uuid); |
| suggestion_in_ask = true; |
| } |
| |
| // If it exists (and it should), perform the action and clean up |
| if (suggestion) { |
| auto component_url = suggestion->prototype->source_url; |
| std::string log_detail = suggestion->prototype |
| ? short_proposal_str(*suggestion->prototype) |
| : "invalid"; |
| |
| FXL_LOG(INFO) << (interaction.type == |
| fuchsia::modular::InteractionType::SELECTED |
| ? "Accepted" |
| : "Dismissed") |
| << " suggestion " << suggestion_uuid << " (" << log_detail |
| << ")"; |
| |
| debug_->OnSuggestionSelected(suggestion->prototype); |
| |
| auto& proposal = suggestion->prototype->proposal; |
| auto proposal_id = proposal.id; |
| auto preloaded_story_id = suggestion->prototype->preloaded_story_id; |
| suggestion->interrupting = false; |
| switch (interaction.type) { |
| case fuchsia::modular::InteractionType::SELECTED: { |
| if (!preloaded_story_id.empty()) { |
| // Notify before we promote + remove from next since the proposal |
| // won't be available when that happens. |
| if (proposal.listener) { |
| auto listener = proposal.listener.Bind(); |
| listener->OnProposalAccepted(proposal.id, preloaded_story_id); |
| } |
| PromoteNextProposal(component_url, preloaded_story_id, proposal_id); |
| // Only next suggestions can be rich so we can finish early. |
| // No need to remove. |
| return; |
| } |
| PerformActions(std::move(proposal.on_selected), |
| std::move(proposal.listener), proposal.id, |
| proposal.story_name, component_url, proposal.story_id); |
| break; |
| } |
| case fuchsia::modular::InteractionType::DISMISSED: { |
| break; // Remove suggestion since it was dismissed by user. |
| } |
| // These actions . |
| case fuchsia::modular::InteractionType::EXPIRED: |
| case fuchsia::modular::InteractionType::SNOOZED: { |
| // No need to remove since it was either expired by a timeout in |
| // user shell or snoozed by the user. |
| return; |
| } |
| } |
| |
| if (suggestion_in_ask) { |
| query_processor_.CleanUpPreviousQuery(); |
| } else { |
| RemoveNextProposal(component_url, proposal_id); |
| } |
| } else { |
| FXL_LOG(WARNING) << "Requested suggestion prototype not found. UUID: " |
| << suggestion_uuid; |
| } |
| } |
| |
| // |fuchsia::modular::SuggestionEngine| |
| void SuggestionEngineImpl::RegisterProposalPublisher( |
| fidl::StringPtr url, |
| fidl::InterfaceRequest<fuchsia::modular::ProposalPublisher> publisher) { |
| // Check to see if a fuchsia::modular::ProposalPublisher has already been |
| // created for the component with this url. If not, create one. |
| std::unique_ptr<ProposalPublisherImpl>& source = proposal_publishers_[url]; |
| if (!source) { // create if it didn't already exist |
| source = std::make_unique<ProposalPublisherImpl>(this, url); |
| } |
| |
| source->AddBinding(std::move(publisher)); |
| } |
| |
| // |fuchsia::modular::SuggestionEngine| |
| void SuggestionEngineImpl::RegisterQueryHandler( |
| fidl::StringPtr url, fidl::InterfaceHandle<fuchsia::modular::QueryHandler> |
| query_handler_handle) { |
| query_processor_.RegisterQueryHandler(url, std::move(query_handler_handle)); |
| } |
| |
| // |fuchsia::modular::SuggestionEngine| |
| void SuggestionEngineImpl::Initialize( |
| fidl::InterfaceHandle<fuchsia::modular::StoryProvider> story_provider, |
| fidl::InterfaceHandle<fuchsia::modular::FocusProvider> focus_provider, |
| fidl::InterfaceHandle<fuchsia::modular::ContextWriter> context_writer, |
| fidl::InterfaceHandle<fuchsia::modular::ContextReader> context_reader, |
| fidl::InterfaceHandle<fuchsia::modular::PuppetMaster> puppet_master) { |
| story_provider_.Bind(std::move(story_provider)); |
| focus_provider_ptr_.Bind(std::move(focus_provider)); |
| context_reader_.Bind(std::move(context_reader)); |
| query_processor_.Initialize(std::move(context_writer)); |
| puppet_master_.Bind(std::move(puppet_master)); |
| RegisterRankingFeatures(); |
| } |
| |
| // end fuchsia::modular::SuggestionEngine |
| |
| void SuggestionEngineImpl::RegisterRankingFeatures() { |
| // Create common ranking features |
| ranking_features["proposal_hint_rf"] = |
| std::make_shared<ProposalHintRankingFeature>(); |
| ranking_features["kronk_rf"] = std::make_shared<KronkRankingFeature>(); |
| ranking_features["mod_pairs_rf"] = std::make_shared<ModPairRankingFeature>(); |
| ranking_features["query_match_rf"] = |
| std::make_shared<QueryMatchRankingFeature>(); |
| ranking_features["focused_story_rf"] = |
| std::make_shared<FocusedStoryRankingFeature>(); |
| ranking_features["annoyance_rf"] = |
| std::make_shared<AnnoyanceRankingFeature>(); |
| ranking_features["dead_story_rf"] = |
| std::make_shared<DeadStoryRankingFeature>(); |
| ranking_features["is_interrupting_rf"] = |
| std::make_shared<InterruptingRankingFeature>(); |
| |
| // Get context updates every time a story is focused to rerank suggestions |
| // based on the story that is focused at the moment. |
| fuchsia::modular::ContextQuery query; |
| for (auto const& it : ranking_features) { |
| fuchsia::modular::ContextSelectorPtr selector = |
| it.second->CreateContextSelector(); |
| if (selector) { |
| AddToContextQuery(&query, it.first, std::move(*selector)); |
| } |
| } |
| context_reader_->Subscribe(std::move(query), |
| context_listener_binding_.NewBinding()); |
| |
| // TODO(jwnichols): Replace the code configuration of the ranking features |
| // with a configuration file |
| |
| // Set up the next ranking features |
| auto next_ranker = std::make_unique<LinearRanker>(); |
| next_ranker->AddRankingFeature(1.0, ranking_features["proposal_hint_rf"]); |
| next_ranker->AddRankingFeature(-0.1, ranking_features["kronk_rf"]); |
| next_ranker->AddRankingFeature(0, ranking_features["mod_pairs_rf"]); |
| next_ranker->AddRankingFeature(1.0, ranking_features["focused_story_rf"]); |
| next_processor_.SetRanker(std::move(next_ranker)); |
| |
| // Set up the query ranking features |
| auto query_ranker = std::make_unique<LinearRanker>(); |
| query_ranker->AddRankingFeature(1.0, ranking_features["proposal_hint_rf"]); |
| query_ranker->AddRankingFeature(-0.1, ranking_features["kronk_rf"]); |
| query_ranker->AddRankingFeature(0, ranking_features["mod_pairs_rf"]); |
| query_ranker->AddRankingFeature(0, ranking_features["query_match_rf"]); |
| query_processor_.SetRanker(std::move(query_ranker)); |
| |
| // Set up the interrupt ranking features |
| auto interrupt_ranker = std::make_unique<LinearRanker>(); |
| interrupt_ranker->AddRankingFeature(1.0, ranking_features["annoyance_rf"]); |
| auto decision_policy = std::make_unique<RankOverThresholdDecisionPolicy>( |
| std::move(interrupt_ranker)); |
| next_processor_.SetInterruptionDecisionPolicy(std::move(decision_policy)); |
| |
| // Set up passive filters |
| std::vector<std::unique_ptr<SuggestionPassiveFilter>> passive_filters; |
| passive_filters.push_back(std::make_unique<ConjugateRankedPassiveFilter>( |
| ranking_features["focused_story_rf"])); |
| passive_filters.push_back(std::make_unique<RankedPassiveFilter>( |
| ranking_features["is_interrupting_rf"])); |
| next_processor_.SetPassiveFilters(std::move(passive_filters)); |
| } |
| |
| void SuggestionEngineImpl::PerformActions( |
| fidl::VectorPtr<fuchsia::modular::Action> actions, |
| fidl::InterfaceHandle<fuchsia::modular::ProposalListener> listener, |
| const std::string& proposal_id, const std::string& story_name, |
| const std::string& source_url, const std::string& proposal_story_id) { |
| if (story_name.empty()) { |
| ExecuteActions(std::move(actions), std::move(listener), proposal_id, |
| proposal_story_id); |
| return; |
| } |
| const std::string key = StoryNameKey(source_url, story_name); |
| auto it = story_name_mapping_.find(key); |
| if (it == story_name_mapping_.end()) { |
| story_provider_->CreateStory( |
| nullptr /* module_url */, |
| fxl::MakeCopyable([this, actions = std::move(actions), |
| listener = std::move(listener), proposal_id, |
| story_name, source_url]( |
| const fidl::StringPtr& story_id) mutable { |
| story_name_mapping_[StoryNameKey(source_url, story_name)] = story_id; |
| // TODO(miguelfrde): better expect clients to send focus action? |
| focus_provider_ptr_->Request(story_id); |
| ExecuteActions(std::move(actions), std::move(listener), proposal_id, |
| story_id); |
| })); |
| } else { |
| ExecuteActions(std::move(actions), std::move(listener), proposal_id, |
| it->second); |
| } |
| } |
| |
| void SuggestionEngineImpl::ExecuteActions( |
| fidl::VectorPtr<fuchsia::modular::Action> actions, |
| fidl::InterfaceHandle<fuchsia::modular::ProposalListener> listener, |
| const std::string& proposal_id, const std::string& override_story_id) { |
| for (auto& action : *actions) { |
| switch (action.Which()) { |
| // TODO(miguelfrde): CreateStory is deprecated, remove. |
| case fuchsia::modular::Action::Tag::kCreateStory: { |
| // If we are overriding a story, no need to create a new story. Ignore |
| // this action. |
| if (override_story_id.empty()) { |
| PerformCreateStoryAction(action, std::move(listener), proposal_id); |
| } else { |
| FXL_LOG(INFO) << "Ignored CreateStory action since we are overriding " |
| << " a story."; |
| } |
| break; |
| } |
| case fuchsia::modular::Action::Tag::kFocusStory: { |
| PerformFocusStoryAction(action, override_story_id); |
| break; |
| } |
| case fuchsia::modular::Action::Tag::kFocusModule: { |
| PerformFocusModuleAction(action, override_story_id); |
| break; |
| } |
| case fuchsia::modular::Action::Tag::kAddModule: { |
| PerformAddModuleAction(action, std::move(listener), proposal_id, |
| override_story_id); |
| break; |
| } |
| case fuchsia::modular::Action::Tag::kQueryAction: { |
| PerformQueryAction(action); |
| break; |
| } |
| case fuchsia::modular::Action::Tag::kSetLinkValueAction: { |
| PerformSetLinkValueAction(action, override_story_id); |
| break; |
| } |
| case fuchsia::modular::Action::Tag::kUpdateModule: { |
| PerformUpdateModuleAction(&action, override_story_id); |
| break; |
| } |
| case fuchsia::modular::Action::Tag::kCustomAction: { |
| PerformCustomAction(&action); |
| break; |
| } |
| default: |
| FXL_LOG(WARNING) << "Unknown action tag " << (uint32_t)action.Which(); |
| } |
| } |
| |
| if (listener) { |
| auto proposal_listener = listener.Bind(); |
| proposal_listener->OnProposalAccepted(proposal_id, override_story_id); |
| } |
| } |
| |
| void SuggestionEngineImpl::PerformCreateStoryAction( |
| const fuchsia::modular::Action& action, |
| fidl::InterfaceHandle<fuchsia::modular::ProposalListener> listener, |
| const std::string& proposal_id) { |
| auto activity = debug_->GetIdleWaiter()->RegisterOngoingActivity(); |
| auto& create_story = action.create_story(); |
| |
| if (!story_provider_) { |
| FXL_LOG(WARNING) << "Unable to add module; no story provider"; |
| return; |
| } |
| |
| fuchsia::modular::Intent intent; |
| create_story.intent.Clone(&intent); |
| |
| if (intent.handler) { |
| FXL_LOG(INFO) << "Creating story with module " << intent.handler; |
| } else { // intent.action |
| FXL_LOG(INFO) << "Creating story with action " << intent.action; |
| } |
| |
| story_provider_->CreateStory( |
| nullptr /* module_url */, |
| fxl::MakeCopyable([this, listener = std::move(listener), proposal_id, |
| intent = std::move(intent), |
| activity](const fidl::StringPtr& story_id) mutable { |
| fuchsia::modular::StoryControllerPtr story_controller; |
| story_provider_->GetController(story_id, story_controller.NewRequest()); |
| // TODO(thatguy): We give the first module the name "root". We'd like to |
| // move away from module names being assigned by the framework or other |
| // components, and rather have clients always provide a module name. |
| story_controller->AddModule(nullptr /* parent module path */, |
| "root" /* module name */, std::move(intent), |
| nullptr /* surface relation */); |
| focus_provider_ptr_->Request(story_id); |
| |
| if (listener) { |
| auto proposal_listener = listener.Bind(); |
| proposal_listener->OnProposalAccepted(proposal_id, story_id); |
| } |
| })); |
| } |
| |
| void SuggestionEngineImpl::PerformFocusStoryAction( |
| const fuchsia::modular::Action& action, |
| const std::string& override_story_id) { |
| const auto& focus_story = action.focus_story(); |
| std::string story_id = focus_story.story_id; |
| if (!override_story_id.empty()) { |
| story_id = override_story_id; |
| if (override_story_id != focus_story.story_id) { |
| FXL_LOG(WARNING) |
| << "story_id provided on fuchsia::modular::Proposal (" |
| << override_story_id |
| << ") does not match that on fuchsia::modular::FocusStory action (" |
| << focus_story.story_id << "). Using " << override_story_id << "."; |
| } |
| } |
| FXL_LOG(INFO) << "Requesting focus for story_id " << story_id; |
| focus_provider_ptr_->Request(story_id); |
| } |
| |
| void SuggestionEngineImpl::PerformFocusModuleAction( |
| const fuchsia::modular::Action& action, const std::string& story_id) { |
| if (story_id.empty()) { |
| FXL_LOG(WARNING) << "Unable to focus module; no story id provided"; |
| return; |
| } |
| fuchsia::modular::StoryControllerPtr story_controller; |
| story_provider_->GetController(story_id, story_controller.NewRequest()); |
| auto module_path = action.focus_module().module_path.Clone(); |
| if (module_path->empty()) { |
| FXL_LOG(WARNING) << "Unable to focus module; no module path provided"; |
| return; |
| } |
| |
| fuchsia::modular::ModuleControllerPtr mod_controller; |
| story_controller->GetModuleController(std::move(module_path), |
| mod_controller.NewRequest()); |
| mod_controller->Focus(); |
| } |
| |
| void SuggestionEngineImpl::PerformAddModuleAction( |
| const fuchsia::modular::Action& action, |
| fidl::InterfaceHandle<fuchsia::modular::ProposalListener> listener, |
| const std::string& proposal_id, const std::string& override_story_id) { |
| if (!story_provider_) { |
| FXL_LOG(WARNING) << "Unable to add module; no story provider"; |
| return; |
| } |
| const auto& add_module = action.add_module(); |
| const auto& module_name = add_module.module_name; |
| std::string story_id = add_module.story_id; |
| |
| if (!override_story_id.empty()) { |
| story_id = override_story_id; |
| if (!add_module.story_id->empty() && |
| override_story_id != add_module.story_id) { |
| FXL_LOG(WARNING) |
| << "story_id provided on fuchsia::modular::Proposal (" |
| << override_story_id |
| << ") does not match that on fuchsia::modular::AddModule action (" |
| << add_module.story_id << "). Using " << override_story_id << "."; |
| } |
| } |
| |
| FXL_CHECK(!story_id.empty()) |
| << "Attempting to add module without specifying a story id."; |
| |
| auto parent_module_path = add_module.surface_parent_module_path.Clone(); |
| |
| fuchsia::modular::StoryControllerPtr story_controller; |
| story_provider_->GetController(story_id, story_controller.NewRequest()); |
| fuchsia::modular::Intent intent; |
| fidl::Clone(add_module.intent, &intent); |
| story_controller->AddModule(parent_module_path.Clone(), module_name, |
| std::move(intent), |
| fidl::MakeOptional(add_module.surface_relation)); |
| |
| // Focus the added module. |
| parent_module_path.push_back(module_name); |
| fuchsia::modular::ModuleControllerPtr mod_controller; |
| story_controller->GetModuleController(std::move(parent_module_path), |
| mod_controller.NewRequest()); |
| mod_controller->Focus(); |
| |
| // Notify with story id where the mod was added. This notifications will be |
| // done in a central place once the refactor into puppet master is completed. |
| // For now we notify here to be able to send the right story id in which the |
| // mod was added: aadd_module.story_id or override_story_id. |
| if (listener) { |
| auto proposal_listener = listener.Bind(); |
| proposal_listener->OnProposalAccepted(proposal_id, story_id); |
| } |
| } |
| |
| void SuggestionEngineImpl::PerformUpdateModuleAction( |
| fuchsia::modular::Action* const action, const std::string& story_id) { |
| if (!story_provider_) { |
| FXL_LOG(WARNING) << "Unable to set entity; no story provider"; |
| return; |
| } |
| |
| fuchsia::modular::StoryControllerPtr story_controller; |
| story_provider_->GetController(story_id, story_controller.NewRequest()); |
| story_controller->GetModules(fxl::MakeCopyable( |
| [this, story_controller = std::move(story_controller), |
| module_name = std::move(action->update_module().module_name), |
| parameters = std::move(action->update_module().parameters)]( |
| fidl::VectorPtr<fuchsia::modular::ModuleData> module_datas) { |
| for (const auto& module_data : *module_datas) { |
| if (module_data.module_path != module_name) { |
| continue; |
| } |
| for (auto& parameter : *parameters) { |
| for (auto& entry : *module_data.parameter_map.entries) { |
| if (entry.name != parameter.name) { |
| continue; |
| } |
| |
| fuchsia::modular::LinkPtr link; |
| story_controller->GetLink(fidl::Clone(entry.link_path), |
| link.NewRequest()); |
| switch (parameter.data.Which()) { |
| case fuchsia::modular::IntentParameterData::Tag:: |
| kEntityReference: { |
| link->SetEntity(parameter.data.entity_reference()); |
| break; |
| } |
| case fuchsia::modular::IntentParameterData::Tag::kJson: { |
| fuchsia::mem::Buffer data; |
| parameter.data.json().Clone(&data); |
| link->Set(nullptr, std::move(data)); |
| break; |
| } |
| case fuchsia::modular::IntentParameterData::Tag::kEntityType: |
| case fuchsia::modular::IntentParameterData::Tag::kLinkName: |
| case fuchsia::modular::IntentParameterData::Tag::kLinkPath: |
| case fuchsia::modular::IntentParameterData::Tag::Invalid: { |
| FXL_LOG(WARNING) << "fuchsia::modular::UpdateModule action " |
| "with unsupported " |
| << "parameter data tag #" |
| << (uint32_t)parameter.data.Which(); |
| break; |
| } |
| } |
| } |
| } |
| } |
| })); |
| } |
| |
| void SuggestionEngineImpl::PerformCustomAction( |
| fuchsia::modular::Action* action) { |
| action->custom_action().Bind()->Execute(); |
| } |
| |
| void SuggestionEngineImpl::PerformSetLinkValueAction( |
| const fuchsia::modular::Action& action, const std::string& story_id) { |
| if (!story_provider_) { |
| FXL_LOG(WARNING) << "Unable to set entity; no story provider"; |
| return; |
| } |
| |
| fuchsia::modular::StoryControllerPtr story_controller; |
| story_provider_->GetController(story_id, story_controller.NewRequest()); |
| |
| const auto& set_link_value = action.set_link_value_action(); |
| fuchsia::modular::LinkPtr link; |
| |
| story_controller->GetLink(fidl::Clone(set_link_value.link_path), |
| link.NewRequest()); |
| fsl::SizedVmo vmo; |
| FXL_CHECK(fsl::VmoFromString(*set_link_value.value, &vmo)); |
| link->Set(nullptr, std::move(vmo).ToTransport()); |
| } |
| |
| void SuggestionEngineImpl::PerformQueryAction( |
| const fuchsia::modular::Action& action) { |
| // TODO(miguelfrde): instead of keeping a AutoSelectFirstQueryListener as an |
| // attribute. Create and move here through an internal structure. |
| const auto& query_action = action.query_action(); |
| Query(auto_select_first_query_listener_binding_.NewBinding(), |
| query_action.input, kQueryActionMaxResults); |
| } |
| |
| void SuggestionEngineImpl::OnContextUpdate( |
| fuchsia::modular::ContextUpdate update) { |
| for (auto& entry : update.values.take()) { |
| for (const auto& rf_it : ranking_features) { |
| if (entry.key == rf_it.first) { // Update key == rf key |
| rf_it.second->UpdateContext(entry.value); |
| } |
| } |
| } |
| next_processor_.UpdateRanking(); |
| } |
| |
| std::string SuggestionEngineImpl::StoryIdFromName( |
| const std::string& source_url, const std::string& story_name) { |
| auto it = story_name_mapping_.find(StoryNameKey(source_url, story_name)); |
| if (it != story_name_mapping_.end()) { |
| return it->second; |
| } |
| return ""; |
| } |
| |
| bool SuggestionEngineImpl::CanComponentUseRichSuggestions( |
| const std::string& component_url) { |
| // Only kronk is allowed to preload stories in suggestions to make |
| // rich suggestions. |
| // Proposinator is used for testing. |
| return component_url.find("kronk") != std::string::npos || |
| component_url.find("Proposinator") != std::string::npos; |
| } |
| |
| } // namespace modular |