JD2022-TU1/main/extern/UbiServices/custom/client-sdk/private/ubiservices/applicationStateManager.cpp

769 lines
36 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// (c) Copyright, Ubisoft Entertainment Inc. || Created by Harbour //
////////////////////////////////////////////////////////////////////////////////
#include "ubiservices/applicationStateManager.h"
#include "ubiservices/core/configs/configpriv.h"
#include "ubiservices/core/chrono/chronometer.h"
#include "ubiservices/core/chrono/clockSystem.h"
#include "ubiservices/core/debug/assert.h"
#include "ubiservices/core/helpers/facadesManager.h"
#include "ubiservices/core/helpers/helpers.h"
#include "ubiservices/core/helpers/instancesManager.h"
#include "ubiservices/core/locks/criticalSection.h"
#include "ubiservices/core/locks/scopedCS.h"
#include "ubiservices/core/log/eventLogHelper.h"
#include "ubiservices/core/log/remoteLoggerHelper.h"
#include "ubiservices/core/notifications/notificationSource.inl"
#include "ubiservices/core/tasks/asyncResultBatch.h"
#include "ubiservices/core/tasks/asyncResultInternal.h"
#include "ubiservices/core/tasks/JobManager.h"
#include "ubiservices/core/tasks/scheduler.h"
#include "ubiservices/facadeInternal.h"
#include "ubiservices/core/helpers/platformHelper/platformHelperProxy.h"
#include "ubiservices/services/authentication/suspendedMode/jobResumeFocusMaster.h"
#include "ubiservices/services/authentication/suspendedMode/jobResumeFromSuspended.h"
#include "ubiservices/services/event/eventClientImpl.h"
#include "ubiservices/services/event/eventClientProxy.h"
#include "ubiservices/services/parameters/parametersSettings.h"
#if defined(US_TARGET_WITH_GDK)
#include <appnotify.h>
#endif
namespace US_NS {;
// Note 1 : We use a macro instead of a function in order to keep US_FILE and US_LINE accurate
// Note 2 : We need to be thread safe in this macro and take into account that a Facade can be removed
// while executing this macro. So we keep the facade alive by copying the SmartPtr while the remote log
// is generated.
#define US_WRITE_LOG_OR_REMOTE_LOG(instancesManagerPtr, logLevel, log) \
do { \
if ((instancesManagerPtr) != nullptr) \
{ \
SmartPtr<FacadeInternal> firstFacade = ApplicationStateManager_BF::getFirstFacade(*instancesManagerPtr); \
if (firstFacade != nullptr) \
{ \
US_LOG_REMOTE(*firstFacade, (logLevel), LogCategory::Core, (log)); \
} \
else \
{ \
US_LOG((logLevel), LogCategory::Core, (log)); \
} \
} \
else \
{ \
US_LOG((logLevel), LogCategory::Core, (log)); \
} \
} ONCE
namespace ApplicationStateManager_BF
{
SmartPtr<FacadeInternal> getFirstFacade(InstancesManager& im)
{
// Note : We need to be thread safe in this function and take into account that a Facade can removed
// while executing this function. So we first retrieve a copy of the facade list (using smartPtr)
// And we return a SmartPtr on the facade, so that the FacadeInternal won't be deleted while using it.
const Vector<SmartPtr<FacadeInternal>> facadesList = im.getFacadesManager().getFacadesList();
return (facadesList.size() > 0)
? im.getFacadesManager().getFacadesList().front()
: nullptr;
}
}
#if defined(US_TARGET_WITH_GDK)
class ApplicationEventsManager final
{
public:
ApplicationEventsManager()
: m_appStateChangeRoutine(nullptr)
, m_appContrainsChangeRoutine(nullptr)
{
InstancesManager* ctrInstancesManager = US_NS::InstancesManager::getInstance();
StringStream ssRegisterFail;
ssRegisterFail << "Function CreateEventEx returned invalid handle for suspended/background mode event registration.\n";
ssRegisterFail << "Several things will not be completely working as intended: Primary and secondary store updates, many club/uplay related features and others.\n";
ssRegisterFail << "In order to work, this function must be called from the main UI thread using\n";
ssRegisterFail << "US_NS::configureSdk().\n";
ssRegisterFail << "Note : This is necessary only on XBoxOne & Scarlett platforms";
const HRESULT resultSuspend = RegisterAppStateChangeNotification(
callbackStateChangeNotification, nullptr, &m_appStateChangeRoutine);
if (FAILED(resultSuspend))
{
US_WRITE_LOG_OR_REMOTE_LOG(ctrInstancesManager, LogLevel::Warning, ssRegisterFail.getContent());
}
const HRESULT resultConstrained = RegisterAppConstrainedChangeNotification(
callbackConstrainedChangeNotification, nullptr, &m_appContrainsChangeRoutine);
if (FAILED(resultConstrained))
{
US_WRITE_LOG_OR_REMOTE_LOG(ctrInstancesManager, LogLevel::Warning, ssRegisterFail.getContent());
}
}
~ApplicationEventsManager()
{
UnregisterAppStateChangeNotification(m_appStateChangeRoutine);
UnregisterAppConstrainedChangeNotification(m_appContrainsChangeRoutine);
}
private:
static void callbackStateChangeNotification(BOOLEAN suspending, PVOID context)
{
US_UNUSED(context);
StringStream ssSuspended;
ssSuspended << "ApplicationStateManager::ApplicationEventsManager received RegisterAppStateChangeNotification. Application state change to ";
InstancesManager* instancesManager = US_NS::InstancesManager::getInstance();
if (suspending)
{
s_isFirstPartySuspended = true;
ssSuspended << "SUSPENDED.";
US_WRITE_LOG_OR_REMOTE_LOG(instancesManager, LogLevel::Warning, ssSuspended.getContent());
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Suspended);
}
else
{
s_isFirstPartySuspended = false;
ssSuspended << "NOT-SUSPENDED.";
US_WRITE_LOG_OR_REMOTE_LOG(instancesManager, LogLevel::Warning, ssSuspended.getContent());
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Foreground);
}
}
static void callbackConstrainedChangeNotification(BOOLEAN constrained, PVOID context)
{
US_UNUSED(context);
StringStream ssConstrained;
ssConstrained << "ApplicationStateManager::ApplicationEventsManager received RegisterAppConstrainedChangeNotification. Application state change to ";
InstancesManager* instancesManager = US_NS::InstancesManager::getInstance();
if (constrained)
{
ssConstrained << "CONSTRAINED.";
US_WRITE_LOG_OR_REMOTE_LOG(instancesManager, LogLevel::Warning, ssConstrained.getContent());
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Background);
}
else
{
ssConstrained << "NOT-CONSTRAINED.";
US_WRITE_LOG_OR_REMOTE_LOG(instancesManager, LogLevel::Warning, ssConstrained.getContent());
if (s_isFirstPartySuspended)
{
// In this case, we receive a notification "back from constrained" but the device is still in suspended mode
// The UbiServices SDK should NOT go to foreground in such case, because the device is actually still suspended,
// and calls to the first party API will fail because of being suspended.
// The notification is ignored.
US_WRITE_LOG_OR_REMOTE_LOG(instancesManager, LogLevel::Warning, "Platform is in suspended mode, the back-from-constrained notification is ignored");
}
else
{
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Foreground);
}
}
}
PAPPSTATE_REGISTRATION m_appStateChangeRoutine;
PAPPCONSTRAIN_REGISTRATION m_appContrainsChangeRoutine;
static bool s_isFirstPartySuspended;
};
bool ApplicationEventsManager::s_isFirstPartySuspended = false;
#elif defined(US_TARGET_XONE)
namespace PlatformHelper_BF
{
StateNotificationType convertStateToEvent(PlatformState state);
}
ref class ApplicationEventsManager sealed
{
public:
ApplicationEventsManager()
{
using Windows::ApplicationModel::Core::CoreApplicationView;
using Windows::ApplicationModel::Core::CoreApplication;
m_handlerToken1 = CoreApplication::ResourceAvailabilityChanged::add(
ref new Windows::Foundation::EventHandler<Platform::Object^>(this, &ApplicationEventsManager::OnResourceAvailabilityChanged));
m_handlerToken2 = CoreApplication::Suspending::add(
ref new Windows::Foundation::EventHandler<Windows::ApplicationModel::SuspendingEventArgs^>(this, &ApplicationEventsManager::OnSuspending));
m_handlerToken3 = CoreApplication::Resuming::add(
ref new Windows::Foundation::EventHandler<Platform::Object^>(this, &ApplicationEventsManager::OnResuming));
CoreApplicationView^ mainView = CoreApplication::MainView;
US_ASSERT(mainView != nullptr, "Unexpected behaviour.");
// Note : Registering to Activated and VisibilityChanged events must be performed from the thread that launched
// Windows::ApplicationModel::Core::IFrameworkView::SetWindow(), otherwise, the += operation raises a WrongThreadException
// Do a first try in the current thread
try {
mainView->CoreWindow->Activated +=
ref new Windows::Foundation::TypedEventHandler<Windows::UI::Core::CoreWindow^,
Windows::UI::Core::WindowActivatedEventArgs^>(this, &ApplicationEventsManager::OnActivated);
mainView->CoreWindow->VisibilityChanged +=
ref new Windows::Foundation::TypedEventHandler<Windows::UI::Core::CoreWindow^,
Windows::UI::Core::VisibilityChangedEventArgs^>(this, &ApplicationEventsManager::OnVisibilityChanged);
}
catch (Platform::WrongThreadException^ exception)
{
// The current thread cannot register to events. Try with RunAsync(), this will run from the thread that calls
// Windows::UI::Core::CoreDispatcher::ProcessEvents()
mainView->CoreWindow->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Low,
ref new Windows::UI::Core::DispatchedHandler
([=]()
{
CoreApplicationView^ mainView = CoreApplication::MainView;
try
{
mainView->CoreWindow->Activated +=
ref new Windows::Foundation::TypedEventHandler<Windows::UI::Core::CoreWindow^,
Windows::UI::Core::WindowActivatedEventArgs^>(this, &ApplicationEventsManager::OnActivated);
mainView->CoreWindow->VisibilityChanged +=
ref new Windows::Foundation::TypedEventHandler<Windows::UI::Core::CoreWindow^,
Windows::UI::Core::VisibilityChangedEventArgs^>(this, &ApplicationEventsManager::OnVisibilityChanged);
}
catch (Platform::WrongThreadException^ e)
{
// This thread is still not the correct one. Printing an error log and rethrow the exception
US_LOG(LogLevel::Error, LogCategory::Core, "Couldn't register to the XBoxOne events for background detection. "
"Several things will not be completely working as intended : Primary and secondary store updates, "
"many club related features and others. In order to work, one of the following methods needs to be"
"run from the thread that calls the Windows::ApplicationModel::Core::IFrameworkView::SetWindow() method : "
"US_NS::configureSdk() or Windows::UI::Core::CoreDispatcher::ProcessEvents(). "
"Note : This is necessary only on XBoxOne platform");
throw;
}
}
));
}
}
virtual ~ApplicationEventsManager()
{
Windows::ApplicationModel::Core::CoreApplication::ResourceAvailabilityChanged -= m_handlerToken1;
Windows::ApplicationModel::Core::CoreApplication::Suspending -= m_handlerToken2;
Windows::ApplicationModel::Core::CoreApplication::Resuming -= m_handlerToken3;
}
void OnActivated(Windows::UI::Core::CoreWindow^ applicationView, Windows::UI::Core::WindowActivatedEventArgs^ args)
{
switch (args->WindowActivationState)
{
case Windows::UI::Core::CoreWindowActivationState::CodeActivated:
case Windows::UI::Core::CoreWindowActivationState::PointerActivated:
US_LOG(LogLevel::Debug, LogCategory::Core, "ApplicationStateManager: OnActivated where WindowActivationState is CodeActivated/PointerActivated");
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Foreground);
break;
case Windows::UI::Core::CoreWindowActivationState::Deactivated:
US_LOG(LogLevel::Debug, LogCategory::Core, "ApplicationStateManager: OnActivated where WindowActivationState is Deactivated");
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Background);
break;
default:
break;
}
}
void OnVisibilityChanged(Windows::UI::Core::CoreWindow^ applicationView, Windows::UI::Core::VisibilityChangedEventArgs^ args)
{
US_LOG(LogLevel::Debug, LogCategory::Core, "ApplicationStateManager: OnVisibilityChanged to " << (args->Visible ? "Visible" : "Invisible"));
US_NS::PlatformHelperProxy::changeState(args->Visible
? US_NS::PlatformState::Foreground
: US_NS::PlatformState::Background);
}
void OnResourceAvailabilityChanged(Platform::Object^ sender, Platform::Object^ args)
{
using Windows::ApplicationModel::Core::CoreApplication;
using Windows::ApplicationModel::Core::ResourceAvailability;
InstancesManager* im = InstancesManager::getInstance();
US_WRITE_LOG_OR_REMOTE_LOG(im, LogLevel::Warning, US_STREAM(
"ApplicationStateManager: OnResourceAvailabilityChanged into ResourceAvailability::" << (
CoreApplication::ResourceAvailability == ResourceAvailability::Full ? "Full" :
CoreApplication::ResourceAvailability == ResourceAvailability::Constrained ? "Constrained" :
CoreApplication::ResourceAvailability == ResourceAvailability::FullWithExtendedSystemReserve ? "FullWithExtendedSystemReserve" : "Unknown")));
US_UNUSED(sender);
US_UNUSED(args);
switch (CoreApplication::ResourceAvailability)
{
case ResourceAvailability::Full:
case ResourceAvailability::FullWithExtendedSystemReserve:
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Foreground);
break;
case ResourceAvailability::Constrained:
break;
default:
US_ASSERT(false, "Unexpected enumeration value");
break;
}
}
void OnSuspending(Platform::Object^ sender, Windows::ApplicationModel::SuspendingEventArgs^ args)
{
US_LOG(LogLevel::Debug, LogCategory::Core, "ApplicationStateManager: OnSuspending");
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Suspended);
}
void OnResuming(Platform::Object^ sender, Platform::Object^ args)
{
US_LOG(LogLevel::Debug, LogCategory::Core ,"ApplicationStateManager: OnResuming");
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Foreground);
}
private:
Windows::Foundation::EventRegistrationToken m_handlerToken1;
Windows::Foundation::EventRegistrationToken m_handlerToken2;
Windows::Foundation::EventRegistrationToken m_handlerToken3;
};
#endif
ApplicationStateManager::ApplicationStateManager()
: m_jobManager(US_NEW(JobManager, MaxConcurrentCalls, "ApplicationStateManager"))
, m_currentSdkState(UbiServicesSdkStateInternal::Foreground)
, m_mustSwitchSdkToNextState(false)
, m_backgroundToForeground(Vector<Facade*>(), "ApplicationStateManager::m_backgroundToForeground")
, m_suspendedToForeground(Vector<Facade*>(), "ApplicationStateManager::m_suspendedToForeground")
, m_stateLock("stateLock", LockMasks::Global) // LockGlobal since cancel is called inside this critical section
, m_switchLock("switchLock", LockMasks::Last) // LockLast since it protects only a flag used only in this class
#if defined(US_TARGET_XONE) || defined(US_TARGET_MANJARO)
#if defined(US_TARGET_WITH_GDK)
, m_applicationEventsManager(US_NEW(ApplicationEventsManager))
#else
, m_applicationEventsManager(ref new ApplicationEventsManager())
#endif
, m_timeSinceLastTransitionFromSuspended(US_NEW(HighResolutionChrono,
ClockSteady::getNanoFromMilli(ParametersSettings::s_DEFAULT_VALUE_RESUME_FROM_SUSPENDED_DELAY_MSEC)))
#endif
{
}
ApplicationStateManager::~ApplicationStateManager()
{
#if defined(US_TARGET_XONE) || defined(US_TARGET_MANJARO)
#if defined(US_TARGET_WITH_GDK)
US_DELETE(m_applicationEventsManager);
#else
m_applicationEventsManager = nullptr;
#endif
US_DELETE(m_timeSinceLastTransitionFromSuspended);
#endif
}
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionTo(InstancesManager& im,
PlatformState toState)
{
US_PERF_TASK_FUNC();
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > > result(US_WHERE());
ScopedCS cs(m_stateLock);
updateSdkState();
switch (m_currentSdkState)
{
case UbiServicesSdkStateInternal::Foreground:
return transitionFromForegroundTo(im, toState, result);
case UbiServicesSdkStateInternal::Background:
return transitionFromBackgroundTo(im, toState, result);
case UbiServicesSdkStateInternal::Suspended:
return transitionFromSuspendedTo(im, toState, result);
case UbiServicesSdkStateInternal::BackgroundToForeground:
return transitionFromBackgroundToForegroundTo(im, toState, result);
case UbiServicesSdkStateInternal::SuspendedToForeground:
return transitionFromSuspendedToForegroundTo(im, toState, result);
case UbiServicesSdkStateInternal::SuspendedToForegroundToBackground:
return transitionFromSuspendedToForegroundToBackgroundTo(im, toState, result);
default:
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::RESUME_INVALID_STATE),
"UbiServices SDK is currently in an unknown/corrupted state. Reset the state to Foreground");
m_currentSdkState = UbiServicesSdkStateInternal::Foreground;
return AsyncResultBatch<Facade*, Empty>(result);
}
}
PlatformState ApplicationStateManager::getApplicationState()
{
ScopedCS cs(m_stateLock);
updateSdkState();
switch (m_currentSdkState)
{
case UbiServicesSdkStateInternal::Foreground:
return PlatformState::Foreground;
case UbiServicesSdkStateInternal::Background:
case UbiServicesSdkStateInternal::BackgroundToForeground:
return PlatformState::Background;
case UbiServicesSdkStateInternal::Suspended:
case UbiServicesSdkStateInternal::SuspendedToForeground:
case UbiServicesSdkStateInternal::SuspendedToForegroundToBackground:
return PlatformState::Suspended;
default:
US_ASSERT(false, US_STREAM_CSTR("UbiServicesSDKState is unknown/corrupted: " << m_currentSdkState));
return PlatformState::Foreground; // Return Foreground arbitrarily
}
}
void ApplicationStateManager::switchToNextState()
{
// This function is called from the scheduler thread, inside the scheduler lock.
// So updating the flag m_currentSdkState here cannot be done since the m_stateLock
// has a higher level than the scheduler lock (it could cause a dealock)
// In order to keep the flag m_currentSdkState thread-safe
// just raise a flag here to trigger the update of the state before using the state flags again
ScopedCS csSwitch(m_switchLock);
m_mustSwitchSdkToNextState = true;
}
UbiServicesSdkStateInternal::Enum ApplicationStateManager::getUbiServicesSdkStateInternal()
{
return m_currentSdkState;
}
void ApplicationStateManager::updateSdkState()
{
ScopedCS csSwitch(m_switchLock);
if (m_mustSwitchSdkToNextState)
{
switch (m_currentSdkState)
{
case UbiServicesSdkStateInternal::BackgroundToForeground:
case UbiServicesSdkStateInternal::SuspendedToForeground:
m_currentSdkState = UbiServicesSdkStateInternal::Foreground;
break;
case UbiServicesSdkStateInternal::SuspendedToForegroundToBackground:
m_currentSdkState = UbiServicesSdkStateInternal::Background;
break;
default:
// In other sdk states, there is no transition in progress,
// So just keep the current state
break;
}
m_mustSwitchSdkToNextState = false;
}
}
// Must be called inside the m_stateLock critical section
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionFromForegroundTo(InstancesManager& im,
PlatformState toState,
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
{
switch (toState)
{
case PlatformState::Foreground:
// Keep the current state, do nothing
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Warning,
"Previous transition already led to Foreground (Current state : Foreground). New call to transitionTo(Foreground) has no effect.");
SET_ASYNC_TO_SUCCESS(result, "Success");
break;
case PlatformState::Background:
goToBackground(im, result);
break;
case PlatformState::Suspended:
goToSuspended(im, result);
break;
default:
US_ASSERT(false, US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::SDK_STATE_TRANSITION_TO_INVALID), US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
break;
}
return AsyncResultBatch<Facade*, Empty>(result);
}
// Must be called inside the m_stateLock critical section
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionFromBackgroundTo(InstancesManager& im, PlatformState toState,
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
{
switch (toState)
{
case PlatformState::Foreground:
resumeFromBackground(im, result);
return m_backgroundToForeground;
case PlatformState::Background:
// Keep the current state, do nothing
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Warning,
"Previous transition already led to Background (Current state : Background). New call to transitionTo(Background) has no effect.");
SET_ASYNC_TO_SUCCESS(result, "Success");
break;
case PlatformState::Suspended:
goToSuspended(im, result);
break;
default:
US_ASSERT(false, US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::SDK_STATE_TRANSITION_TO_INVALID), US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
break;
}
return AsyncResultBatch<Facade*, Empty>(result);
}
// Must be called inside the m_stateLock critical section
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionFromSuspendedTo(InstancesManager& im, PlatformState toState,
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
{
switch (toState)
{
case PlatformState::Foreground:
resumeFromSuspended(im, result);
return m_suspendedToForeground;
case PlatformState::Background:
// This transition is not supported, suspended to background should only be on the first-party side
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::RESUME_INVALID_STATE),
"Transition to Foreground is required");
break;
case PlatformState::Suspended:
{
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Warning,
"Previous transition already led to Suspended (Current state : Suspended). New call to transitionTo(Suspended) has no effect.");
SET_ASYNC_TO_SUCCESS(result, "Success");
}
break;
default:
US_ASSERT(false, US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::SDK_STATE_TRANSITION_TO_INVALID), US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
break;
}
return AsyncResultBatch<Facade*, Empty>(result);
}
// Must be called inside the m_stateLock critical section
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionFromBackgroundToForegroundTo(InstancesManager& im, PlatformState toState,
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
{
switch (toState)
{
case PlatformState::Foreground:
// Already transitioning to Foreground, just return the existing asyncResult
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Warning,
"Transition Background -> Foreground already processing. Return the AsyncResult of the previous transition");
return m_backgroundToForeground;
case PlatformState::Background:
// cancel previous async to avoid creating many jobs if the player is spamming the button "foreground <-> background"
m_backgroundToForeground.cancel();
goToBackground(im, result);
break;
case PlatformState::Suspended:
goToSuspended(im, result);
break;
default:
US_ASSERT(false, US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::SDK_STATE_TRANSITION_TO_INVALID), US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
break;
}
return AsyncResultBatch<Facade*, Empty>(result);
}
// Must be called inside the m_stateLock critical section
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionFromSuspendedToForegroundTo(InstancesManager& im, PlatformState toState,
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
{
switch (toState)
{
case PlatformState::Foreground:
// Already transitioning to Foreground, just return the existing AsyncResult
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Warning,
"Transition Suspended -> Foreground already processing. Return the AsyncResult of the previous transition");
return m_suspendedToForeground;
case PlatformState::Background:
// The SDK is still resuming from suspended mode.
// Return the AsyncResult indicating when the suspended mode will be finished
// Nothing to do more since the "going to background" transition is not doing anything.
m_currentSdkState = UbiServicesSdkStateInternal::SuspendedToForegroundToBackground;
return m_suspendedToForeground;
case PlatformState::Suspended:
goToSuspended(im, result);
break;
default:
US_ASSERT(false, US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::SDK_STATE_TRANSITION_TO_INVALID), US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
break;
}
return AsyncResultBatch<Facade*, Empty>(result);
}
// Must be called inside the m_stateLock critical section
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionFromSuspendedToForegroundToBackgroundTo(InstancesManager& im, PlatformState toState,
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
{
switch (toState)
{
case PlatformState::Foreground:
// The SDK is still resuming from suspended mode, and the application went to background
// Return the AsyncResult indicating when the suspended mode will be finished.
// Nothing to do more since steps done in "background to foreground" transition
// are also done in the "suspended to foreground" transition.
// This could mean a bit out of date data, but it allows avoiding spamming the server with requests
m_currentSdkState = UbiServicesSdkStateInternal::SuspendedToForeground;
return m_suspendedToForeground;
case PlatformState::Background:
// The SDK is still resuming from suspended mode, and the application goes to background
// Return the AsyncResult indicating when the suspended mode will be finished.
// State of the SDK is kept to SuspendedToForegroundToBackground
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Warning,
"Previous transition already leads to Background (Current state : Suspended). New call to transitionTo(Background) has no effect.");
return m_suspendedToForeground;
case PlatformState::Suspended:
goToSuspended(im, result);
break;
default:
US_ASSERT(false, US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::SDK_STATE_TRANSITION_TO_INVALID), US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
break;
}
return AsyncResultBatch<Facade*, Empty>(result);
}
// Must be called inside the m_stateLock critical section
void ApplicationStateManager::goToBackground(InstancesManager& im, AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
{
#if defined (US_TARGET_XONE) || defined(US_TARGET_MANJARO)
im.resetBackgroundChrono();
#else
US_UNUSED(im);
#endif
// Nothing to do, just return success
SET_ASYNC_TO_SUCCESS(result, "Success");
m_currentSdkState = UbiServicesSdkStateInternal::Background;
}
// Must be called inside the m_stateLock critical section
void ApplicationStateManager::resumeFromBackground(InstancesManager& im, AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
{
// Launch the job that will do the check when resuming from background (namely if the profile has been linked)
const Vector<SmartPtr<FacadeInternal> > facades = im.getFacadesManager().getFacadesList();
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Info,
"UbiServices SDK resuming from background mode.");
m_jobManager->launch(result, US_NEW(JobResumeFocusMaster, result, facades, ClockSystem::TTimeNano(im.getBackgroundTime())));
// Store the async result in case new transitions would need this to be returned
m_backgroundToForeground = AsyncResultBatch<Facade*, Empty>(result);
m_currentSdkState = UbiServicesSdkStateInternal::BackgroundToForeground;
}
// Must be called inside the m_stateLock critical section
void ApplicationStateManager::goToSuspended(InstancesManager& im, AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
{
#if defined(US_TARGET_WITH_GDK)
im.networkEnginesShutdown();
#elif defined(US_TARGET_XONE)
// Force close on websockets due to a Xone OS bug
im.closeWebsocketsOnSuspending();
#endif
// Stop the scheduler thread
// Note : There could be a thread-safety issue here if resumeFromSuspended is in progress
// However, this would mean susp -> fg -> susp in a very short time, which is unlikely to happen.
// For 3.2, the risk is considered lower than implementing a complex system handling this case.
// Unfortunately, no time to log this at this place...
im.resetSuspendChrono();
im.getScheduler().stopProcessingJobs();
storeEventPeriodicJobRunning(im);
SET_ASYNC_TO_SUCCESS(result, "Success");
m_currentSdkState = UbiServicesSdkStateInternal::Suspended;
}
// Must be called inside the m_stateLock critical section
void ApplicationStateManager::resumeFromSuspended(InstancesManager& im, AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
{
#if defined(US_TARGET_XONE) || defined(US_TARGET_MANJARO)
// Reset chrono when resuming from suspended mode
m_timeSinceLastTransitionFromSuspended->resetState();
#if defined(US_TARGET_WITH_GDK)
im.networkEnginesRelaunch();
#endif
#endif
// Flush all jobs in the scheduler.
im.getScheduler().cancelJobs(Job::CancelType::Suspend);
// Restart scheduler execution
// The existing jobs have been canceled, but are still in the scheduler here.
im.getScheduler().restartProcessingJobs();
const ClockSystem::TTimeNano suspendedDuration = im.getSuspendedTime();
const Vector<SmartPtr<FacadeInternal> > facades = im.getFacadesManager().getFacadesList();
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Info,
String::formatText("UbiServices SDK resuming from suspended mode. Suspend lasted %llu seconds.", ClockSteady::getMilliFromNano(suspendedDuration) / 1000));
// JobResumeFromSuspend updates the s_applicationState flag
m_jobManager->launch(result, US_NEW(JobResumeFromSuspended, result, facades, suspendedDuration, m_eventPeriodicJobsRunning));
m_suspendedToForeground = AsyncResultBatch<Facade*, Empty>(result);
m_currentSdkState = UbiServicesSdkStateInternal::SuspendedToForeground;
}
#if defined(US_TARGET_XONE) || defined(US_TARGET_MANJARO)
const uint64 ApplicationStateManager::getElapseMsecSinceLastTransitionFromSuspended()
{
return ClockSteady::getMilliFromNano(m_timeSinceLastTransitionFromSuspended->getElapsed());
}
#endif
void ApplicationStateManager::storeEventPeriodicJobRunning(InstancesManager& im)
{
// No time for logging here
if (!m_suspendedToForeground.isProcessing())
{
// Do not update the list if a resumeFromSuspend is processing
// In the following case :
// Susp -> fg -> susp
// This would mean the event periodic job would never be restarted,
// because the transition susp -> fp cancels the event periodic job (along with all jobs in the scheduler).
// The state before the first suspend must be kept.
m_eventPeriodicJobsRunning.clear();
Vector<SmartPtr<FacadeInternal> > facades = im.getFacadesManager().getFacadesList();
for (Vector<SmartPtr<FacadeInternal> >::iterator it = facades.begin(); it != facades.end(); ++it)
{
FacadeInternal* facade = reinterpret_cast<FacadeInternal*>(it->getPtr());
m_eventPeriodicJobsRunning[facade] = EventClientProxy::getEventClientImpl(facade->getEventClient()).isPeriodicJobRunning();
}
}
}
#undef US_WRITE_LOG_OR_REMOTE_LOG
};