| // Copyright 2019 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/ui/bin/root_presenter/factory_reset_manager.h" |
| |
| #include <fuchsia/media/cpp/fidl.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| #include <lib/async/time.h> |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| |
| namespace root_presenter { |
| |
| using fuchsia::media::AudioRenderUsage; |
| using fuchsia::media::sounds::Player_AddSoundFromFile_Result; |
| using fuchsia::media::sounds::Player_PlaySound_Result; |
| |
| constexpr uint32_t FACTORY_RESET_SOUND_ID = 0; |
| |
| FactoryResetManager::WatchHandler::WatchHandler( |
| const fuchsia::recovery::ui::FactoryResetCountdownState& state) { |
| state.Clone(¤t_state_); |
| } |
| |
| void FactoryResetManager::WatchHandler::Watch(WatchCallback callback) { |
| hanging_get_ = std::move(callback); |
| SendIfChanged(); |
| } |
| |
| void FactoryResetManager::WatchHandler::OnStateChange( |
| const fuchsia::recovery::ui::FactoryResetCountdownState& state) { |
| state.Clone(¤t_state_); |
| last_state_sent_ = false; |
| SendIfChanged(); |
| } |
| |
| void FactoryResetManager::WatchHandler::SendIfChanged() { |
| if (hanging_get_ && !last_state_sent_) { |
| fuchsia::recovery::ui::FactoryResetCountdownState state_to_send; |
| current_state_.Clone(&state_to_send); |
| hanging_get_(std::move(state_to_send)); |
| last_state_sent_ = true; |
| hanging_get_ = nullptr; |
| } |
| } |
| |
| FactoryResetManager::FactoryResetManager(sys::ComponentContext& context, |
| std::shared_ptr<MediaRetriever> media_retriever) |
| : media_retriever_(media_retriever) { |
| context.outgoing()->AddPublicService<fuchsia::recovery::ui::FactoryResetCountdown>( |
| [this](fidl::InterfaceRequest<fuchsia::recovery::ui::FactoryResetCountdown> request) { |
| auto handler = std::make_unique<WatchHandler>(State()); |
| countdown_bindings_.AddBinding(std::move(handler), std::move(request)); |
| }); |
| |
| context.svc()->Connect(factory_reset_.NewRequest()); |
| FX_DCHECK(factory_reset_); |
| context.svc()->Connect(sound_player_.NewRequest()); |
| FX_DCHECK(sound_player_); |
| } |
| |
| bool FactoryResetManager::OnMediaButtonReport( |
| const fuchsia::ui::input::MediaButtonsReport& report) { |
| switch (factory_reset_state_) { |
| case FactoryResetState::NONE: { |
| return HandleReportOnNoneState(report); |
| } |
| case FactoryResetState::BUTTON_COUNTDOWN: { |
| return HandleReportOnButtonCountdown(report); |
| } |
| case FactoryResetState::RESET_COUNTDOWN: { |
| return HandleReportOnResetCountdown(report); |
| } |
| default: { |
| return false; |
| } |
| } |
| } |
| |
| void FactoryResetManager::PlayCompleteSoundThenReset() { |
| FX_LOGS(DEBUG) << "Playing countdown complete sound"; |
| factory_reset_state_ = FactoryResetState::TRIGGER_RESET; |
| |
| MediaRetriever::ResetSoundResult result = media_retriever_->GetResetSound(); |
| if (result.is_error()) { |
| FX_LOGS(INFO) << "Skipping countdown complete sound. Unable to open audio file: " |
| << zx_status_get_string(result.error()); |
| TriggerFactoryReset(); |
| return; |
| } |
| |
| sound_player_->AddSoundFromFile( |
| FACTORY_RESET_SOUND_ID, std::move(result.value()), |
| [this](Player_AddSoundFromFile_Result result) { |
| if (result.is_response()) { |
| sound_player_->PlaySound(FACTORY_RESET_SOUND_ID, AudioRenderUsage::SYSTEM_AGENT, |
| [this](Player_PlaySound_Result result) { |
| if (result.is_err()) { |
| FX_LOGS(WARNING) |
| << "Failed to play countdown complete sound in player"; |
| } else { |
| sound_player_->RemoveSound(FACTORY_RESET_SOUND_ID); |
| } |
| |
| // Trigger reset after sound completes, otherwise sound |
| // is cut off. Reset regardless of whether the sound |
| // played successfully or not. |
| TriggerFactoryReset(); |
| }); |
| } else { |
| FX_LOGS(WARNING) << "Failed to add countdown complete sound to player"; |
| // If we couldn't add the sound, don't bother trying to play |
| // the sound, just trigger the reset early. |
| TriggerFactoryReset(); |
| } |
| }); |
| } |
| |
| void FactoryResetManager::TriggerFactoryReset() { |
| FX_LOGS(WARNING) << "Triggering factory reset"; |
| FX_DCHECK(factory_reset_); |
| factory_reset_->Reset([](zx_status_t status) { |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Factory service failed with status: " << zx_status_get_string(status); |
| } |
| }); |
| } |
| |
| void FactoryResetManager::NotifyStateChange() { |
| for (auto& binding : countdown_bindings_.bindings()) { |
| if (binding->is_bound()) { |
| binding->impl()->OnStateChange(State()); |
| } |
| } |
| } |
| |
| fuchsia::recovery::ui::FactoryResetCountdownState FactoryResetManager::State() const { |
| fuchsia::recovery::ui::FactoryResetCountdownState state; |
| if (factory_reset_state_ == FactoryResetState::RESET_COUNTDOWN) { |
| state.set_scheduled_reset_time(deadline_); |
| } |
| return state; |
| } |
| |
| bool FactoryResetManager::HandleReportOnNoneState( |
| const fuchsia::ui::input::MediaButtonsReport& report) { |
| if (!report.reset) { |
| return false; |
| } |
| |
| factory_reset_state_ = FactoryResetState::BUTTON_COUNTDOWN; |
| start_reset_countdown_after_timeout_.Reset( |
| fit::bind_member(this, &FactoryResetManager::StartFactoryResetCountdown)); |
| async::PostDelayedTask(async_get_default_dispatcher(), |
| start_reset_countdown_after_timeout_.callback(), kButtonCountdownDuration); |
| return true; |
| } |
| |
| bool FactoryResetManager::HandleReportOnButtonCountdown( |
| const fuchsia::ui::input::MediaButtonsReport& report) { |
| // If the reset button is no longer held, cancel the button countdown. Otherwise, ignore the |
| // report. |
| if (!report.reset) { |
| start_reset_countdown_after_timeout_.Cancel(); |
| factory_reset_state_ = FactoryResetState::NONE; |
| } |
| |
| return true; |
| } |
| |
| bool FactoryResetManager::HandleReportOnResetCountdown( |
| const fuchsia::ui::input::MediaButtonsReport& report) { |
| // If the reset button is no longer held, cancel the reset countdown and notify the state change. |
| // Otherwise, ignore the report. |
| if (!report.reset) { |
| FX_LOGS(WARNING) << "Factory reset canceled"; |
| reset_after_timeout_.Cancel(); |
| factory_reset_state_ = FactoryResetState::NONE; |
| deadline_ = ZX_TIME_INFINITE_PAST; |
| NotifyStateChange(); |
| } |
| |
| return true; |
| } |
| |
| void FactoryResetManager::StartFactoryResetCountdown() { |
| if (factory_reset_state_ == FactoryResetState::RESET_COUNTDOWN) { |
| return; |
| } |
| |
| FX_LOGS(WARNING) << "Starting factory reset countdown"; |
| factory_reset_state_ = FactoryResetState::RESET_COUNTDOWN; |
| deadline_ = async_now(async_get_default_dispatcher()) + kResetCountdownDuration.get(); |
| NotifyStateChange(); |
| |
| reset_after_timeout_.Reset( |
| fit::bind_member(this, &FactoryResetManager::PlayCompleteSoundThenReset)); |
| async::PostDelayedTask(async_get_default_dispatcher(), reset_after_timeout_.callback(), |
| kResetCountdownDuration); |
| } |
| |
| } // namespace root_presenter |