380 lines
12 KiB
C++
380 lines
12 KiB
C++
// 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
|