| // 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/sys/activity/state_machine_driver.h" | 
 |  | 
 | #include <fuchsia/ui/activity/cpp/fidl.h> | 
 | #include <lib/async/cpp/task.h> | 
 | #include <lib/async/cpp/time.h> | 
 | #include <lib/fit/function.h> | 
 | #include <lib/syslog/cpp/macros.h> | 
 | #include <lib/zx/time.h> | 
 | #include <lib/zx/timer.h> | 
 | #include <zircon/assert.h> | 
 | #include <zircon/status.h> | 
 | #include <zircon/types.h> | 
 |  | 
 | #include <map> | 
 |  | 
 | #include "src/lib/fxl/memory/weak_ptr.h" | 
 | #include "src/sys/activity/activity_state_machine.h" | 
 | #include "src/sys/activity/common.h" | 
 |  | 
 | namespace activity { | 
 |  | 
 | fuchsia::ui::activity::State StateMachineDriver::GetState() const { | 
 |   return override_state_ ? *override_state_ : state_machine_.state(); | 
 | } | 
 |  | 
 | zx_status_t StateMachineDriver::RegisterObserver(ObserverId id, StateChangedCallback callback) { | 
 |   observers_.emplace(id, std::move(callback)); | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | zx_status_t StateMachineDriver::UnregisterObserver(ObserverId id) { | 
 |   auto entry = observers_.find(id); | 
 |   if (entry == observers_.end()) { | 
 |     return ZX_ERR_NOT_FOUND; | 
 |   } | 
 |   observers_.erase(entry); | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | zx_status_t StateMachineDriver::ReceiveDiscreteActivity( | 
 |     const fuchsia::ui::activity::DiscreteActivity& activity, zx::time time, VoidCallback callback) { | 
 |   if (time < last_transition_time_) { | 
 |     callback(); | 
 |     return ZX_ERR_OUT_OF_RANGE; | 
 |   } | 
 |   auto event = ActivityStateMachine::EventForDiscreteActivity(activity); | 
 |   return async::PostTaskForTime( | 
 |       dispatcher_, | 
 |       [weak = weak_factory_.GetWeakPtr(), event, time, callback = std::move(callback)] { | 
 |         if (weak) { | 
 |           weak->ProcessEvent(event, time); | 
 |         } | 
 |         callback(); | 
 |       }, | 
 |       time); | 
 | } | 
 |  | 
 | zx_status_t StateMachineDriver::StartOngoingActivity(OngoingActivityId id, zx::time time, | 
 |                                                      VoidCallback callback) { | 
 |   if (time < last_transition_time_) { | 
 |     callback(); | 
 |     return ZX_ERR_OUT_OF_RANGE; | 
 |   } | 
 |   return async::PostTaskForTime( | 
 |       dispatcher_, | 
 |       [weak = weak_factory_.GetWeakPtr(), id, time, callback = std::move(callback)] { | 
 |         if (weak) { | 
 |           auto event = ActivityStateMachine::EventForOngoingActivityStart(); | 
 |           weak->ProcessActivityStart(id); | 
 |           weak->ProcessEvent(event, time); | 
 |         } | 
 |         callback(); | 
 |       }, | 
 |       time); | 
 | } | 
 |  | 
 | zx_status_t StateMachineDriver::EndOngoingActivity(OngoingActivityId id, zx::time time, | 
 |                                                    VoidCallback callback) { | 
 |   if (time < last_transition_time_) { | 
 |     callback(); | 
 |     return ZX_ERR_OUT_OF_RANGE; | 
 |   } | 
 |   return async::PostTaskForTime( | 
 |       dispatcher_, | 
 |       [weak = weak_factory_.GetWeakPtr(), id, time, callback = std::move(callback)] { | 
 |         if (weak) { | 
 |           auto event = ActivityStateMachine::EventForOngoingActivityEnd(); | 
 |           weak->ProcessActivityEnd(id); | 
 |           weak->ProcessEvent(event, time); | 
 |         } | 
 |         callback(); | 
 |       }, | 
 |       time); | 
 | } | 
 |  | 
 | void StateMachineDriver::SetOverrideState(std::optional<fuchsia::ui::activity::State> state) { | 
 |   bool should_notify = (state.has_value() != override_state_.has_value()) || | 
 |                        (override_state_ && (state != override_state_)); | 
 |   override_state_ = state; | 
 |   if (should_notify) { | 
 |     FX_LOGS(INFO) << "activity-service: entering state '" << GetState() << "'"; | 
 |     NotifyObservers(GetState(), async::Now(dispatcher_)); | 
 |   } | 
 | } | 
 |  | 
 | void StateMachineDriver::ProcessEvent(const Event& event, zx::time time) { | 
 |   auto state = state_machine_.state(); | 
 |   state_machine_.ReceiveEvent(event); | 
 |   auto new_state = state_machine_.state(); | 
 |  | 
 |   if (state != new_state) { | 
 |     last_transition_time_ = time; | 
 |     if (!override_state_) { | 
 |       FX_LOGS(INFO) << "activity-service: '" << state << "' -> '" << new_state << "' due to '" | 
 |                     << event << "'"; | 
 |       NotifyObservers(new_state, time); | 
 |     } | 
 |   } | 
 |  | 
 |   timeout_task_.Cancel(); | 
 |   auto timeout = ActivityStateMachine::TimeoutFor(new_state); | 
 |   if (ongoing_activities_.empty() && timeout) { | 
 |     StartTimer(*timeout); | 
 |   } | 
 | } | 
 |  | 
 | void StateMachineDriver::ProcessActivityStart(OngoingActivityId id) { | 
 |   if (ongoing_activities_.find(id) != ongoing_activities_.end()) { | 
 |     FX_LOGS(WARNING) << "Activity '" << id << "' already started, ignoring"; | 
 |     return; | 
 |   } | 
 |   ongoing_activities_.insert(id); | 
 |   if (timeout_task_.is_pending()) { | 
 |     timeout_task_.Cancel(); | 
 |   } | 
 | } | 
 |  | 
 | void StateMachineDriver::ProcessActivityEnd(OngoingActivityId id) { | 
 |   auto iter = ongoing_activities_.find(id); | 
 |   if (iter == ongoing_activities_.end()) { | 
 |     FX_LOGS(WARNING) << "Activity '" << id << "' spuriously ended, ignoring"; | 
 |     return; | 
 |   } | 
 |   ongoing_activities_.erase(iter); | 
 |   if (!timeout_task_.is_pending()) { | 
 |     auto timeout = ActivityStateMachine::TimeoutFor(state_machine_.state()); | 
 |     if (timeout) { | 
 |       StartTimer(*timeout); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void StateMachineDriver::StartTimer(zx::duration delay) { | 
 |   if (timeout_task_.is_pending() && | 
 |       async::Now(dispatcher_) + delay < timeout_task_.last_deadline()) { | 
 |     return; | 
 |   } | 
 |   timeout_task_.Cancel(); | 
 |   timeout_task_.PostDelayed(dispatcher_, delay); | 
 | } | 
 |  | 
 | void StateMachineDriver::HandleTimeout() { | 
 |   auto status = async::PostTask(dispatcher_, [weak = weak_factory_.GetWeakPtr()]() { | 
 |     if (weak) { | 
 |       weak->ProcessEvent(Event::TIMEOUT, async::Now(weak->dispatcher_)); | 
 |     } | 
 |   }); | 
 |   if (status != ZX_OK) { | 
 |     FX_LOGS(ERROR) << "activity-service: Failed to queue timeout event: " | 
 |                    << zx_status_get_string(status); | 
 |   } | 
 | } | 
 |  | 
 | void StateMachineDriver::NotifyObservers(fuchsia::ui::activity::State state, zx::time time) const { | 
 |   for (const auto& observer : observers_) { | 
 |     observer.second(state, time); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace activity |