| // Copyright 2018 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/basemgr/basemgr_impl.h" |
| |
| #include <memory> |
| |
| #include <fuchsia/auth/cpp/fidl.h> |
| #include <fuchsia/modular/auth/cpp/fidl.h> |
| #include <fuchsia/modular/cpp/fidl.h> |
| #include <fuchsia/sys/cpp/fidl.h> |
| #include <fuchsia/ui/policy/cpp/fidl.h> |
| #include <fuchsia/ui/viewsv1/cpp/fidl.h> |
| #include <fuchsia/ui/viewsv1token/cpp/fidl.h> |
| #include <lib/async/cpp/future.h> |
| #include <lib/component/cpp/startup_context.h> |
| #include <lib/fidl/cpp/interface_handle.h> |
| #include <lib/fidl/cpp/interface_request.h> |
| #include <lib/fidl/cpp/string.h> |
| #include <lib/fxl/logging.h> |
| #include <lib/fxl/macros.h> |
| |
| #include "peridot/bin/basemgr/basemgr_settings.h" |
| #include "peridot/bin/basemgr/user_provider_impl.h" |
| #include "peridot/lib/common/async_holder.h" |
| #include "peridot/lib/common/names.h" |
| #include "peridot/lib/common/teardown.h" |
| #include "peridot/lib/fidl/app_client.h" |
| #include "peridot/lib/fidl/clone.h" |
| #include "peridot/lib/session_shell_settings/session_shell_settings.h" |
| #include "peridot/lib/util/filesystem.h" |
| |
| namespace modular { |
| |
| BasemgrImpl::BasemgrImpl( |
| const modular::BasemgrSettings& settings, |
| const std::vector<SessionShellSettings>& session_shell_settings, |
| fuchsia::sys::Launcher* const launcher, |
| fuchsia::ui::policy::PresenterPtr presenter, |
| fuchsia::devicesettings::DeviceSettingsManagerPtr device_settings_manager, |
| std::function<void()> on_shutdown) |
| : settings_(settings), |
| session_shell_settings_(session_shell_settings), |
| launcher_(launcher), |
| presenter_(std::move(presenter)), |
| device_settings_manager_(std::move(device_settings_manager)), |
| on_shutdown_(std::move(on_shutdown)), |
| user_provider_impl_("UserProviderImpl"), |
| base_shell_context_binding_(this), |
| authentication_context_provider_binding_(this) { |
| UpdateSessionShellConfig(); |
| |
| // TODO(SCN-595): Presentation is now discoverable, so we don't need |
| // kPresentationService anymore. |
| service_namespace_.AddService(presentation_state_.bindings.GetHandler( |
| presentation_state_.presentation.get()), |
| kPresentationService); |
| |
| Start(); |
| } |
| |
| BasemgrImpl::~BasemgrImpl() = default; |
| |
| void BasemgrImpl::Connect( |
| fidl::InterfaceRequest<fuchsia::modular::internal::BasemgrDebug> request) { |
| basemgr_bindings_.AddBinding(this, std::move(request)); |
| } |
| |
| void BasemgrImpl::InitializePresentation( |
| fidl::InterfaceHandle<fuchsia::ui::viewsv1token::ViewOwner> view_owner) { |
| if (settings_.test && !settings_.enable_presenter) { |
| return; |
| } |
| |
| auto presentation_request = |
| presentation_state_.presentation.is_bound() |
| ? presentation_state_.presentation.Unbind().NewRequest() |
| : presentation_state_.presentation.NewRequest(); |
| |
| presenter_->Present2(zx::eventpair(view_owner.TakeChannel().release()), |
| std::move(presentation_request)); |
| |
| AddGlobalKeyboardShortcuts(presentation_state_.presentation); |
| |
| SetShadowTechnique(presentation_state_.shadow_technique); |
| |
| // Set the presentation of the given view to the settings of the active |
| // session shell. |
| if (active_session_shell_settings_index_ >= session_shell_settings_.size()) { |
| FXL_LOG(ERROR) << "Active session shell index is " |
| << active_session_shell_settings_index_ << ", but only " |
| << session_shell_settings_.size() |
| << " session shell settings exist."; |
| return; |
| } |
| |
| auto active_session_shell_settings = |
| session_shell_settings_[active_session_shell_settings_index_]; |
| if (active_session_shell_settings.display_usage != |
| fuchsia::ui::policy::DisplayUsage::kUnknown) { |
| FXL_DLOG(INFO) << "Setting display usage: " |
| << fidl::ToUnderlying( |
| active_session_shell_settings.display_usage); |
| presentation_state_.presentation->SetDisplayUsage( |
| active_session_shell_settings.display_usage); |
| } |
| |
| if (!std::isnan(active_session_shell_settings.screen_width) && |
| !std::isnan(active_session_shell_settings.screen_height)) { |
| FXL_DLOG(INFO) << "Setting display size: " |
| << active_session_shell_settings.screen_width << " x " |
| << active_session_shell_settings.screen_height; |
| presentation_state_.presentation->SetDisplaySizeInMm( |
| active_session_shell_settings.screen_width, |
| active_session_shell_settings.screen_height); |
| } |
| } |
| |
| void BasemgrImpl::StartBaseShell() { |
| if (base_shell_running_) { |
| FXL_DLOG(INFO) << "StartBaseShell() called when already running"; |
| |
| return; |
| } |
| |
| base_shell_app_ = std::make_unique<AppClient<fuchsia::modular::Lifecycle>>( |
| launcher_, CloneStruct(settings_.base_shell)); |
| base_shell_app_->services().ConnectToService(base_shell_.NewRequest()); |
| |
| fuchsia::ui::viewsv1::ViewProviderPtr base_shell_view_provider; |
| base_shell_app_->services().ConnectToService( |
| base_shell_view_provider.NewRequest()); |
| |
| // We still need to pass a request for root view to base shell since |
| // dev_base_shell (which mimics flutter behavior) blocks until it receives |
| // the root view request. |
| fidl::InterfaceHandle<fuchsia::ui::viewsv1token::ViewOwner> root_view; |
| base_shell_view_provider->CreateView(root_view.NewRequest(), nullptr); |
| |
| InitializePresentation(std::move(root_view)); |
| |
| // Populate parameters and initialize the base shell. |
| fuchsia::modular::BaseShellParams params; |
| params.presentation = std::move(presentation_state_.presentation); |
| base_shell_->Initialize(base_shell_context_binding_.NewBinding(), |
| std::move(params)); |
| |
| base_shell_running_ = true; |
| } |
| |
| FuturePtr<> BasemgrImpl::StopBaseShell() { |
| if (!base_shell_running_) { |
| FXL_DLOG(INFO) << "StopBaseShell() called when already stopped"; |
| |
| return Future<>::CreateCompleted("StopBaseShell::Completed"); |
| } |
| |
| auto did_stop = Future<>::Create("StopBaseShell"); |
| |
| base_shell_app_->Teardown(kBasicTimeout, [did_stop, this] { |
| FXL_DLOG(INFO) << "- fuchsia::modular::BaseShell down"; |
| |
| base_shell_running_ = false; |
| did_stop->Complete(); |
| }); |
| |
| return did_stop; |
| } |
| |
| FuturePtr<> BasemgrImpl::StopTokenManagerFactoryApp() { |
| if (!token_manager_factory_app_) { |
| FXL_DLOG(INFO) |
| << "StopTokenManagerFactoryApp() called when already stopped"; |
| |
| return Future<>::CreateCompleted("StopTokenManagerFactoryApp::Completed"); |
| } |
| |
| auto did_stop = Future<>::Create("StopTokenManagerFactoryApp"); |
| |
| token_manager_factory_app_->Teardown(kBasicTimeout, [did_stop, this] { |
| FXL_DLOG(INFO) << "- fuchsia::auth::TokenManagerFactory down"; |
| |
| token_manager_factory_app_.release(); |
| did_stop->Complete(); |
| }); |
| |
| return did_stop; |
| } |
| |
| void BasemgrImpl::Start() { |
| if (settings_.test) { |
| // 0. Print test banner. |
| FXL_LOG(INFO) |
| << std::endl |
| << std::endl |
| << "======================== Starting Test [" << settings_.test_name |
| << "]" << std::endl |
| << "============================================================" |
| << std::endl; |
| } |
| |
| // Wait for persistent data to come up. |
| if (!settings_.no_minfs) { |
| WaitForMinfs(); |
| } |
| |
| // Start OAuth Token Manager App. |
| token_manager_factory_app_.release(); |
| fuchsia::modular::AppConfig token_manager_config; |
| token_manager_config.url = settings_.account_provider.url; |
| token_manager_factory_app_ = |
| std::make_unique<AppClient<fuchsia::modular::Lifecycle>>( |
| launcher_, CloneStruct(token_manager_config)); |
| token_manager_factory_app_->services().ConnectToService( |
| token_manager_factory_.NewRequest()); |
| |
| user_provider_impl_.reset(new UserProviderImpl( |
| launcher_, settings_.sessionmgr, session_shell_config_, |
| settings_.story_shell, token_manager_factory_.get(), |
| authentication_context_provider_binding_.NewBinding().Bind(), this)); |
| |
| ShowSetupOrLogin(); |
| |
| ReportEvent(ModularEvent::BOOTED_TO_BASEMGR); |
| } |
| |
| void BasemgrImpl::GetUserProvider( |
| fidl::InterfaceRequest<fuchsia::modular::UserProvider> request) { |
| user_provider_impl_->Connect(std::move(request)); |
| } |
| |
| void BasemgrImpl::Shutdown() { |
| // Prevent the shutdown sequence from running twice. |
| if (state_ == State::SHUTTING_DOWN) { |
| return; |
| } |
| |
| state_ = State::SHUTTING_DOWN; |
| |
| FXL_DLOG(INFO) << "fuchsia::modular::BaseShellContext::Shutdown()"; |
| |
| if (settings_.test) { |
| FXL_LOG(INFO) |
| << std::endl |
| << "============================================================" |
| << std::endl |
| << "======================== [" << settings_.test_name << "] Done"; |
| } |
| |
| // TODO(mesch): Some of these could be done in parallel too. |
| // fuchsia::modular::UserProvider must go first, but the order after user |
| // provider is for now rather arbitrary. We terminate base shell last so |
| // that in tests testing::Teardown() is invoked at the latest possible time. |
| // Right now it just demonstrates that AppTerminate() works as we like it |
| // to. |
| user_provider_impl_.Teardown(kUserProviderTimeout, [this] { |
| FXL_DLOG(INFO) << "- fuchsia::modular::UserProvider down"; |
| StopTokenManagerFactoryApp()->Then([this] { |
| FXL_DLOG(INFO) << "- fuchsia::auth::TokenManagerFactory down"; |
| StopBaseShell()->Then([this] { |
| FXL_LOG(INFO) << "Clean Shutdown"; |
| on_shutdown_(); |
| }); |
| }); |
| }); |
| } |
| |
| void BasemgrImpl::GetAuthenticationUIContext( |
| fidl::InterfaceRequest<fuchsia::auth::AuthenticationUIContext> request) { |
| // TODO(MI4-1107): Basemgr needs to implement AuthenticationUIContext |
| // itself, and proxy calls for StartOverlay & StopOverlay to BaseShell, |
| // starting it if it's not running yet. |
| FXL_CHECK(base_shell_); |
| base_shell_->GetAuthenticationUIContext(std::move(request)); |
| } |
| |
| void BasemgrImpl::DidLogin() { |
| // Continues if `enable_presenter` is set to true during testing, as |
| // ownership of the Presenter should still be moved to the session shell. |
| if (settings_.test && !settings_.enable_presenter) { |
| // TODO(MI4-1117): Integration tests currently expect base shell to |
| // always be running. So, if we're running under a test, do not shut down |
| // the base shell after login. |
| return; |
| } |
| |
| // TODO(MI4-1117): See above. The base shell shouldn't be shut down. |
| if (!settings_.test) { |
| FXL_DLOG(INFO) << "Stopping base shell due to login"; |
| StopBaseShell(); |
| } |
| |
| InitializePresentation(session_shell_view_owner_); |
| } |
| |
| void BasemgrImpl::DidLogout() { |
| if (settings_.test) { |
| // TODO(MI4-1117): Integration tests currently expect base shell to |
| // always be running. So, if we're running under a test, DidLogin() will |
| // not shut down the base shell after login; thus this method doesn't |
| // need to re-start the base shell after a logout. |
| return; |
| } |
| |
| FXL_DLOG(INFO) << "Re-starting base shell due to logout"; |
| |
| StartBaseShell(); |
| } |
| |
| fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> |
| BasemgrImpl::GetSessionShellViewOwner( |
| fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner>) { |
| return session_shell_view_owner_.is_bound() |
| ? session_shell_view_owner_.Unbind().NewRequest() |
| : session_shell_view_owner_.NewRequest(); |
| } |
| |
| fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> |
| BasemgrImpl::GetSessionShellServiceProvider( |
| fidl::InterfaceHandle<fuchsia::sys::ServiceProvider>) { |
| fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> handle; |
| service_namespace_.AddBinding(handle.NewRequest()); |
| return handle; |
| } |
| |
| void BasemgrImpl::OnEvent(fuchsia::ui::input::KeyboardEvent event) { |
| switch (event.code_point) { |
| case ' ': { |
| SwapSessionShell(); |
| break; |
| } |
| case 's': { |
| SetNextShadowTechnique(); |
| break; |
| } |
| case 'l': |
| ToggleClipping(); |
| break; |
| default: |
| FXL_DLOG(INFO) << "Unknown keyboard event: codepoint=" << event.code_point |
| << ", modifiers=" << event.modifiers; |
| break; |
| } |
| } |
| |
| void BasemgrImpl::AddGlobalKeyboardShortcuts( |
| fuchsia::ui::policy::PresentationPtr& presentation) { |
| presentation->CaptureKeyboardEventHACK( |
| { |
| .code_point = ' ', // spacebar |
| .modifiers = fuchsia::ui::input::kModifierLeftControl, |
| }, |
| keyboard_capture_listener_bindings_.AddBinding(this)); |
| presentation->CaptureKeyboardEventHACK( |
| { |
| .code_point = 's', |
| .modifiers = fuchsia::ui::input::kModifierLeftControl, |
| }, |
| keyboard_capture_listener_bindings_.AddBinding(this)); |
| presentation->CaptureKeyboardEventHACK( |
| { |
| .code_point = 'l', |
| .modifiers = fuchsia::ui::input::kModifierRightAlt, |
| }, |
| keyboard_capture_listener_bindings_.AddBinding(this)); |
| } |
| |
| void BasemgrImpl::SwapSessionShell() { |
| if (session_shell_settings_.empty()) { |
| FXL_DLOG(INFO) << "No session shells has been defined"; |
| return; |
| } |
| auto shell_count = session_shell_settings_.size(); |
| if (shell_count <= 1) { |
| FXL_DLOG(INFO) |
| << "Only one session shell has been defined so switch is disabled"; |
| return; |
| } |
| active_session_shell_settings_index_ = |
| (active_session_shell_settings_index_ + 1) % shell_count; |
| |
| UpdateSessionShellConfig(); |
| |
| user_provider_impl_->SwapSessionShell(CloneStruct(session_shell_config_)) |
| ->Then([] { FXL_DLOG(INFO) << "Swapped session shell"; }); |
| } |
| |
| void BasemgrImpl::SetNextShadowTechnique() { |
| using ShadowTechnique = fuchsia::ui::gfx::ShadowTechnique; |
| |
| auto next_shadow_technique = |
| [](ShadowTechnique shadow_technique) -> ShadowTechnique { |
| switch (shadow_technique) { |
| case ShadowTechnique::UNSHADOWED: |
| return ShadowTechnique::SCREEN_SPACE; |
| case ShadowTechnique::SCREEN_SPACE: |
| return ShadowTechnique::SHADOW_MAP; |
| default: |
| FXL_LOG(ERROR) << "Unknown shadow technique: " |
| << fidl::ToUnderlying(shadow_technique); |
| // Fallthrough |
| case ShadowTechnique::SHADOW_MAP: |
| case ShadowTechnique::MOMENT_SHADOW_MAP: |
| return ShadowTechnique::UNSHADOWED; |
| } |
| }; |
| |
| SetShadowTechnique( |
| next_shadow_technique(presentation_state_.shadow_technique)); |
| } |
| |
| void BasemgrImpl::SetShadowTechnique( |
| fuchsia::ui::gfx::ShadowTechnique shadow_technique) { |
| if (!presentation_state_.presentation) |
| return; |
| |
| presentation_state_.shadow_technique = shadow_technique; |
| |
| FXL_LOG(INFO) << "Setting shadow technique to " |
| << fidl::ToUnderlying(presentation_state_.shadow_technique); |
| |
| fuchsia::ui::gfx::RendererParam param; |
| param.set_shadow_technique(presentation_state_.shadow_technique); |
| |
| auto renderer_params = |
| fidl::VectorPtr<fuchsia::ui::gfx::RendererParam>::New(0); |
| renderer_params.push_back(std::move(param)); |
| |
| presentation_state_.presentation->SetRendererParams( |
| std::move(renderer_params)); |
| } |
| |
| void BasemgrImpl::ToggleClipping() { |
| if (!presentation_state_.presentation) |
| return; |
| |
| FXL_DLOG(INFO) << "Toggling clipping"; |
| |
| presentation_state_.clipping_enabled = !presentation_state_.clipping_enabled; |
| presentation_state_.presentation->EnableClipping( |
| presentation_state_.clipping_enabled); |
| } |
| |
| void BasemgrImpl::UpdateSessionShellConfig() { |
| // The session shell settings overrides the session_shell flag passed via |
| // command line, except in integration tests. TODO(MF-113): Consolidate |
| // the session shell settings. |
| fuchsia::modular::AppConfig session_shell_config; |
| if (settings_.test || session_shell_settings_.empty()) { |
| session_shell_config = CloneStruct(settings_.session_shell); |
| } else { |
| const auto& settings = |
| session_shell_settings_[active_session_shell_settings_index_]; |
| session_shell_config.url = settings.name; |
| } |
| |
| session_shell_config_ = std::move(session_shell_config); |
| } |
| |
| void BasemgrImpl::ShowSetupOrLogin() { |
| auto show_setup_or_login = [this] { |
| // If the session shell settings specifies it, auto-login as the first |
| // authenticated user. Otherwise, start the base shell to launch setup. |
| if (active_session_shell_settings_index_ < session_shell_settings_.size() && |
| session_shell_settings_[active_session_shell_settings_index_] |
| .auto_login) { |
| user_provider_impl_->PreviousUsers( |
| [this](fidl::VectorPtr<fuchsia::modular::auth::Account> accounts) { |
| if (accounts->empty()) { |
| StartBaseShell(); |
| } else { |
| fuchsia::modular::UserLoginParams params; |
| params.account_id = accounts->at(0).id; |
| user_provider_impl_->Login(std::move(params)); |
| } |
| }); |
| } else { |
| StartBaseShell(); |
| } |
| }; |
| |
| // TODO(MF-134): Improve the factory reset logic by deleting more than just |
| // the user data. |
| // If the device needs factory reset, remove all the users before proceeding |
| // with setup. |
| device_settings_manager_.set_error_handler( |
| [show_setup_or_login](zx_status_t status) { show_setup_or_login(); }); |
| device_settings_manager_->GetInteger( |
| kFactoryResetKey, |
| [this, show_setup_or_login](int factory_reset_value, |
| fuchsia::devicesettings::Status status) { |
| if (status == fuchsia::devicesettings::Status::ok && |
| factory_reset_value > 0) { |
| // Unset the factory reset flag. |
| device_settings_manager_->SetInteger( |
| kFactoryResetKey, 0, [](bool result) { |
| if (!result) { |
| FXL_LOG(WARNING) << "Factory reset flag was not updated."; |
| } |
| }); |
| |
| user_provider_impl_->PreviousUsers( |
| [this]( |
| fidl::VectorPtr<fuchsia::modular::auth::Account> accounts) { |
| std::vector<FuturePtr<>> did_remove_users; |
| did_remove_users.reserve(accounts->size()); |
| |
| for (const auto& account : *accounts) { |
| auto did_remove_user = Future<>::Create( |
| "BasemgrImpl.ShowSetupOrLogin.did_remove_user"); |
| user_provider_impl_->RemoveUser( |
| account.id, |
| [did_remove_user](fidl::StringPtr error_code) { |
| if (error_code) { |
| FXL_LOG(WARNING) << "Account was not removed during " |
| "factory reset. Error code: " |
| << error_code; |
| } |
| did_remove_user->Complete(); |
| }); |
| did_remove_users.emplace_back(did_remove_user); |
| } |
| |
| Wait("BasemgrImpl.ShowSetupOrLogin.Wait", did_remove_users) |
| ->Then([this] { StartBaseShell(); }); |
| }); |
| } else { |
| show_setup_or_login(); |
| } |
| }); |
| } |
| |
| void BasemgrImpl::RestartSession() { user_provider_impl_->RestartSession(); } |
| |
| } // namespace modular |