diff options
Diffstat (limited to 'src/core/hle/service/am/lifecycle_manager.cpp')
-rw-r--r-- | src/core/hle/service/am/lifecycle_manager.cpp | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/src/core/hle/service/am/lifecycle_manager.cpp b/src/core/hle/service/am/lifecycle_manager.cpp new file mode 100644 index 000000000..0dac27ed0 --- /dev/null +++ b/src/core/hle/service/am/lifecycle_manager.cpp @@ -0,0 +1,379 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "core/hle/service/am/lifecycle_manager.h" + +namespace Service::AM { + +LifecycleManager::LifecycleManager(Core::System& system, KernelHelpers::ServiceContext& context, + bool is_application) + : m_system_event(context), m_operation_mode_changed_system_event(context), + m_is_application(is_application) {} + +LifecycleManager::~LifecycleManager() = default; + +Event& LifecycleManager::GetSystemEvent() { + return m_system_event; +} + +Event& LifecycleManager::GetOperationModeChangedSystemEvent() { + return m_operation_mode_changed_system_event; +} + +void LifecycleManager::PushUnorderedMessage(AppletMessage message) { + m_unordered_messages.push_back(message); + this->SignalSystemEventIfNeeded(); +} + +AppletMessage LifecycleManager::PopMessageInOrderOfPriority() { + if (m_has_resume) { + m_has_resume = false; + return AppletMessage::Resume; + } + + if (m_has_acknowledged_exit != m_has_requested_exit) { + m_has_acknowledged_exit = m_has_requested_exit; + return AppletMessage::Exit; + } + + if (m_focus_state_changed_notification_enabled) { + if (!m_is_application) { + if (m_requested_focus_state != m_acknowledged_focus_state) { + m_acknowledged_focus_state = m_requested_focus_state; + switch (m_requested_focus_state) { + case FocusState::InFocus: + return AppletMessage::ChangeIntoForeground; + case FocusState::NotInFocus: + return AppletMessage::ChangeIntoBackground; + default: + ASSERT(false); + } + } + } else if (m_has_focus_state_changed) { + m_has_focus_state_changed = false; + return AppletMessage::FocusStateChanged; + } + } + + if (m_has_requested_request_to_prepare_sleep != m_has_acknowledged_request_to_prepare_sleep) { + m_has_acknowledged_request_to_prepare_sleep = true; + return AppletMessage::RequestToPrepareSleep; + } + + if (m_requested_request_to_display_state != m_acknowledged_request_to_display_state) { + m_acknowledged_request_to_display_state = m_requested_request_to_display_state; + return AppletMessage::RequestToDisplay; + } + + if (m_has_operation_mode_changed) { + m_has_operation_mode_changed = false; + return AppletMessage::OperationModeChanged; + } + + if (m_has_performance_mode_changed) { + m_has_performance_mode_changed = false; + return AppletMessage::PerformanceModeChanged; + } + + if (m_has_sd_card_removed) { + m_has_sd_card_removed = false; + return AppletMessage::SdCardRemoved; + } + + if (m_has_sleep_required_by_high_temperature) { + m_has_sleep_required_by_high_temperature = false; + return AppletMessage::SleepRequiredByHighTemperature; + } + + if (m_has_sleep_required_by_low_battery) { + m_has_sleep_required_by_low_battery = false; + return AppletMessage::SleepRequiredByLowBattery; + } + + if (m_has_auto_power_down) { + m_has_auto_power_down = false; + return AppletMessage::AutoPowerDown; + } + + if (m_has_album_screen_shot_taken) { + m_has_album_screen_shot_taken = false; + return AppletMessage::AlbumScreenShotTaken; + } + + if (m_has_album_recording_saved) { + m_has_album_recording_saved = false; + return AppletMessage::AlbumRecordingSaved; + } + + if (!m_unordered_messages.empty()) { + const auto message = m_unordered_messages.front(); + m_unordered_messages.pop_front(); + return message; + } + + return AppletMessage::None; +} + +bool LifecycleManager::ShouldSignalSystemEvent() { + if (m_focus_state_changed_notification_enabled) { + if (!m_is_application) { + if (m_requested_focus_state != m_acknowledged_focus_state) { + return true; + } + } else if (m_has_focus_state_changed) { + return true; + } + } + + return !m_unordered_messages.empty() || m_has_resume || + (m_has_requested_exit != m_has_acknowledged_exit) || + (m_has_requested_request_to_prepare_sleep != + m_has_acknowledged_request_to_prepare_sleep) || + m_has_operation_mode_changed || m_has_performance_mode_changed || + m_has_sd_card_removed || m_has_sleep_required_by_high_temperature || + m_has_sleep_required_by_low_battery || m_has_auto_power_down || + (m_requested_request_to_display_state != m_acknowledged_request_to_display_state) || + m_has_album_screen_shot_taken || m_has_album_recording_saved; +} + +void LifecycleManager::OnOperationAndPerformanceModeChanged() { + if (m_operation_mode_changed_notification_enabled) { + m_has_operation_mode_changed = true; + } + if (m_performance_mode_changed_notification_enabled) { + m_has_performance_mode_changed = true; + } + m_operation_mode_changed_system_event.Signal(); + this->SignalSystemEventIfNeeded(); +} + +void LifecycleManager::SignalSystemEventIfNeeded() { + // Check our cached value for the system event. + const bool applet_message_available = m_applet_message_available; + + // If it's not current, we need to do an update, either clearing or signaling. + if (applet_message_available != this->ShouldSignalSystemEvent()) { + if (!applet_message_available) { + m_system_event.Signal(); + m_applet_message_available = true; + } else { + m_system_event.Clear(); + m_applet_message_available = false; + } + } +} + +bool LifecycleManager::PopMessage(AppletMessage* out_message) { + const auto message = this->PopMessageInOrderOfPriority(); + this->SignalSystemEventIfNeeded(); + + *out_message = message; + return message != AppletMessage::None; +} + +void LifecycleManager::SetFocusHandlingMode(bool suspend) { + switch (m_focus_handling_mode) { + case FocusHandlingMode::AlwaysSuspend: + case FocusHandlingMode::SuspendHomeSleep: + if (!suspend) { + // Disallow suspension. + m_focus_handling_mode = FocusHandlingMode::NoSuspend; + } + break; + case FocusHandlingMode::NoSuspend: + if (suspend) { + // Allow suspension temporally. + m_focus_handling_mode = FocusHandlingMode::SuspendHomeSleep; + } + break; + } +} + +void LifecycleManager::SetOutOfFocusSuspendingEnabled(bool enabled) { + switch (m_focus_handling_mode) { + case FocusHandlingMode::AlwaysSuspend: + if (!enabled) { + // Allow suspension temporally. + m_focus_handling_mode = FocusHandlingMode::SuspendHomeSleep; + } + break; + case FocusHandlingMode::SuspendHomeSleep: + case FocusHandlingMode::NoSuspend: + if (enabled) { + // Allow suspension. + m_focus_handling_mode = FocusHandlingMode::AlwaysSuspend; + } + break; + } +} + +void LifecycleManager::RemoveForceResumeIfPossible() { + // If resume is not forced, we have nothing to do. + if (m_suspend_mode != SuspendMode::ForceResume) { + return; + } + + // Check activity state. + // If we are already resumed, we can remove the forced state. + switch (m_activity_state) { + case ActivityState::ForegroundVisible: + case ActivityState::ForegroundObscured: + m_suspend_mode = SuspendMode::NoOverride; + return; + + default: + break; + } + + // Check focus handling mode. + switch (m_focus_handling_mode) { + case FocusHandlingMode::AlwaysSuspend: + case FocusHandlingMode::SuspendHomeSleep: + // If the applet allows suspension, we can remove the forced state. + m_suspend_mode = SuspendMode::NoOverride; + break; + + case FocusHandlingMode::NoSuspend: + // If the applet is not an application, we can remove the forced state. + // Only applications can be forced to resume. + if (!m_is_application) { + m_suspend_mode = SuspendMode::NoOverride; + } + } +} + +bool LifecycleManager::IsRunnable() const { + // If suspend is forced, return that. + if (m_forced_suspend) { + return false; + } + + // Check suspend mode override. + switch (m_suspend_mode) { + case SuspendMode::NoOverride: + // Continue processing. + break; + + case SuspendMode::ForceResume: + // The applet is runnable during forced resumption when its exit is requested. + return m_has_requested_exit; + + case SuspendMode::ForceSuspend: + // The applet is never runnable during forced suspension. + return false; + } + + // Always run if exit is requested. + if (m_has_requested_exit) { + return true; + } + + if (m_activity_state == ActivityState::ForegroundVisible) { + // The applet is runnable now. + return true; + } + + if (m_activity_state == ActivityState::ForegroundObscured) { + switch (m_focus_handling_mode) { + case FocusHandlingMode::AlwaysSuspend: + // The applet is not runnable while running the applet. + return false; + + case FocusHandlingMode::SuspendHomeSleep: + // The applet is runnable while running the applet. + return true; + + case FocusHandlingMode::NoSuspend: + // The applet is always runnable. + return true; + } + } + + // The activity is a suspended one. + // The applet should be suspended unless it has disabled suspension. + return m_focus_handling_mode == FocusHandlingMode::NoSuspend; +} + +FocusState LifecycleManager::GetFocusStateWhileForegroundObscured() const { + switch (m_focus_handling_mode) { + case FocusHandlingMode::AlwaysSuspend: + // The applet never learns it has lost focus. + return FocusState::InFocus; + + case FocusHandlingMode::SuspendHomeSleep: + // The applet learns it has lost focus when launching a child applet. + return FocusState::NotInFocus; + + case FocusHandlingMode::NoSuspend: + // The applet always learns it has lost focus. + return FocusState::NotInFocus; + + default: + UNREACHABLE(); + } +} + +FocusState LifecycleManager::GetFocusStateWhileBackground(bool is_obscured) const { + switch (m_focus_handling_mode) { + case FocusHandlingMode::AlwaysSuspend: + // The applet never learns it has lost focus. + return FocusState::InFocus; + + case FocusHandlingMode::SuspendHomeSleep: + // The applet learns it has lost focus when launching a child applet. + return is_obscured ? FocusState::NotInFocus : FocusState::InFocus; + + case FocusHandlingMode::NoSuspend: + // The applet always learns it has lost focus. + return m_is_application ? FocusState::Background : FocusState::NotInFocus; + + default: + UNREACHABLE(); + } +} + +bool LifecycleManager::UpdateRequestedFocusState() { + FocusState new_state{}; + + if (m_suspend_mode == SuspendMode::NoOverride) { + // With no forced suspend or resume, we take the focus state designated + // by the combination of the activity flag and the focus handling mode. + switch (m_activity_state) { + case ActivityState::ForegroundVisible: + new_state = FocusState::InFocus; + break; + + case ActivityState::ForegroundObscured: + new_state = this->GetFocusStateWhileForegroundObscured(); + break; + + case ActivityState::BackgroundVisible: + new_state = this->GetFocusStateWhileBackground(false); + break; + + case ActivityState::BackgroundObscured: + new_state = this->GetFocusStateWhileBackground(true); + break; + + default: + UNREACHABLE(); + } + } else { + // With forced suspend or resume, the applet is guaranteed to be background. + new_state = this->GetFocusStateWhileBackground(false); + } + + if (new_state != m_requested_focus_state) { + // Mark the focus state as ready for update. + m_requested_focus_state = new_state; + + // We changed the focus state. + return true; + } + + // We didn't change the focus state. + return false; +} + +} // namespace Service::AM |