blob: 46583551be9501f2c3418a97d886de986a1ab1ce [file] [log] [blame]
// 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