| // 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/sessionmgr/story_runner/module_controller_impl.h" |
| |
| #include <fuchsia/ui/app/cpp/fidl.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| #include <lib/fidl/cpp/interface_handle.h> |
| #include <lib/fidl/cpp/interface_ptr.h> |
| #include <lib/fidl/cpp/interface_request.h> |
| #include <src/lib/fxl/logging.h> |
| #include <src/lib/fxl/time/time_delta.h> |
| |
| #include "peridot/bin/sessionmgr/story_runner/story_controller_impl.h" |
| #include "peridot/lib/common/teardown.h" |
| #include "peridot/lib/fidl/clone.h" |
| |
| namespace modular { |
| |
| constexpr char kAppStoragePath[] = "/data/APP_DATA"; |
| |
| namespace { |
| |
| // A stopgap solution to map a module's url to a directory name where the |
| // module's /data is mapped. We need three properties here - (1) two module urls |
| // that are the same get mapped to the same hash, (2) two modules urls that are |
| // different don't get the same name (with very high probability) and (3) the |
| // name is visually inspectable. |
| std::string HashModuleUrl(const std::string& module_url) { |
| std::size_t found = module_url.find_last_of('/'); |
| auto last_part = |
| found == module_url.length() - 1 ? "" : module_url.substr(found + 1); |
| return std::to_string(std::hash<std::string>{}(module_url)) + last_part; |
| } |
| |
| }; // namespace |
| |
| ModuleControllerImpl::ModuleControllerImpl( |
| StoryControllerImpl* const story_controller_impl, |
| fuchsia::sys::Launcher* const launcher, |
| fuchsia::modular::AppConfig module_config, |
| const fuchsia::modular::ModuleData* const module_data, |
| fuchsia::sys::ServiceListPtr service_list, |
| fuchsia::ui::views::ViewToken view_token) |
| : story_controller_impl_(story_controller_impl), |
| app_client_( |
| launcher, CloneStruct(module_config), |
| std::string(kAppStoragePath) + HashModuleUrl(module_config.url), |
| std::move(service_list)), |
| module_data_(module_data) { |
| app_client_.SetAppErrorHandler([this] { OnAppConnectionError(); }); |
| |
| fuchsia::ui::app::ViewProviderPtr view_provider; |
| app_client_.services().ConnectToService(view_provider.NewRequest()); |
| view_provider->CreateView(std::move(view_token.value), |
| nullptr /* incoming_services */, |
| nullptr /* outgoing_services */); |
| } |
| |
| ModuleControllerImpl::~ModuleControllerImpl() {} |
| |
| void ModuleControllerImpl::Connect( |
| fidl::InterfaceRequest<fuchsia::modular::ModuleController> request) { |
| module_controller_bindings_.AddBinding(this, std::move(request)); |
| // Notify of initial state on connection. |
| NotifyStateChange(); |
| } |
| |
| // If the ComponentController connection closes, it means the module cannot be |
| // started. We indicate this by the ERROR state. |
| void ModuleControllerImpl::OnAppConnectionError() { |
| SetState(fuchsia::modular::ModuleState::ERROR); |
| } |
| |
| void ModuleControllerImpl::SetState( |
| const fuchsia::modular::ModuleState new_state) { |
| if (state_ == new_state) { |
| return; |
| } |
| |
| state_ = new_state; |
| NotifyStateChange(); |
| } |
| |
| void ModuleControllerImpl::Teardown(fit::function<void()> done) { |
| teardown_done_callbacks_.push_back(std::move(done)); |
| |
| if (teardown_done_callbacks_.size() != 1) { |
| // Not the first request, Stop() in progress. |
| return; |
| } |
| |
| fit::function<void()> cont = [this] { |
| SetState(fuchsia::modular::ModuleState::STOPPED); |
| |
| // We take ownership of *this from |story_controller_impl_| so that |
| // teardown happens in StoryControllerImpl but *this is still alive when we |
| // call |teardown_done_callbacks_|. One or more of the callbacks may be a |
| // result callback for fuchsia::modular::ModuleController::Stop() and since |
| // *this owns the fidl::Binding for the channel on which the result message |
| // will be sent, it must be alive when the message is posted. |
| // TODO(thatguy,mesch): This point is reachable from two distinct |
| // code-paths: originating from ModuleControllerImpl::Stop() or |
| // StoryControllerImpl::Stop(). It is not clear whether ReleaseModule() |
| // must be called *before* these done callbacks are called, or whether we |
| // can move this call below the loop and have ReleaseModule also delete |
| // *this. |
| story_controller_impl_->ReleaseModule(this); |
| |
| for (auto& done : teardown_done_callbacks_) { |
| done(); |
| } |
| |
| // |this| must be deleted after the callbacks so that the |done()| calls |
| // above can be dispatched while the bindings still exist in case they are |
| // FIDL method callbacks. |
| // |
| // The destructor of |this| deletes |app_client_|, which will kill the |
| // related application if it's still running. |
| delete this; |
| }; |
| |
| // At this point, it's no longer an error if the module closes its |
| // connection, or the application exits. |
| app_client_.SetAppErrorHandler(nullptr); |
| |
| // Tear down the module application through the normal procedure with timeout. |
| app_client_.Teardown(kBasicTimeout, std::move(cont)); |
| } |
| |
| void ModuleControllerImpl::Focus() { |
| story_controller_impl_->FocusModule(module_data_->module_path); |
| } |
| |
| void ModuleControllerImpl::Defocus() { |
| story_controller_impl_->DefocusModule(module_data_->module_path); |
| } |
| |
| void ModuleControllerImpl::Stop(StopCallback done) { |
| story_controller_impl_->StopModule(module_data_->module_path, |
| std::move(done)); |
| } |
| |
| void ModuleControllerImpl::NotifyStateChange() { |
| for (auto& binding : module_controller_bindings_.bindings()) { |
| binding->events().OnStateChange(state_); |
| } |
| } |
| |
| } // namespace modular |